library(tidyverse)        # for data cleaning and plotting
library(tidymodels)       # for modeling ... tidily
library(lubridate)        # for date manipulation
library(openintro)        # for the abbr2state() function
library(gplots)           # for col2hex() function
library(RColorBrewer)     # for color palettes
library(ggthemes)         # for more themes (including theme_map())
library(plotly)           # for the ggplotly() - basic interactivity
library(gganimate)        # for adding animation layers to ggplots
library(transformr)       # for "tweening" (gganimate)
library(gifski)           # need the library for creating gifs but don't need to load each time
library(shiny)            # for creating interactive apps
library(janitor)  
library(stacks)            # for stacking models
library(glmnet)            # for regularized regression, including LASSO
library(ranger)            # for random forest model
library(kknn)              # for knn model
library(naniar)            # for examining missing values (NAs)
library(moderndive)        # for King County housing data
library(vip)               # for variable importance plots
library(rmarkdown)         # for paged tables
library(fastDummies)
library(usemodels)         # for suggesting step_XXX() functions
library(readr)
library(kableExtra)
library(DALEX)             # model Agnostic Language for exploration and explanation (for model interpretation)  
library(DALEXtra)
theme_set(theme_minimal())

Introduction

The financial market is a strange place that is very hard to navigate around. We have seen Warren Buffett, Ray Dalio, Charlie Munger - the very very best who have had billions of dollars. Others, 95% of the population, lose the money instead.

So, how are the best of the best pick its stocks? It is from the fundamental, the technical side or the sentimental side? Within this paper, we hope to bring another perspective, using machine learning models to predict the profitability of the stock price.

Paper Outline

First, we will list and explain the definition of each variable in our dataset. There are 25 variables in total as listed below. The variable we are going to predict is PROFIT. Then, we will start to process our data by merging with the macro data and removing some unwanted variables. After processing the data, we will have a data visualization section that shows the distribution and relationship between some variables. And to find the best model, we choose to explore three machine learning models which are LASSO, Random forest and Stacking models. We use a stacking method to create the stacking model by combining three models (Lasso, Random Forest, and KNN). Lastly, we will make a stock return prediction for 2021 using the trained model with the lowest R-squared and RMSE.

List of Variables

For the dataset, we includes financial information on companies in the S&P 500 stock index from 1999-2021. This information was scraped from Yahoo Finance in November of 2021, and collected in a csv format for data analysis. The information includes metrics like sales, earnings, cogs, stock price, and market sector as well as macroeconomic data such as GDP or Money Supply. The goal is to analyze and model this data to better improve projections for a company’s future profitability. The variables in the data set are described below:

Variable Meaning
YEAR The financial year of the company
COMPANY The company’s stock abbreviation symbol
MARKET.CAP The total market capitalization of the company (Volume * Price)
EARNINGS The earnings in dollars for the previous year for the given company
SALES How much the company sold in dollars last year
CASH How much cash the company has in dollars at the end of the previous year
Name The full name of the company
Sector The name of the sector that the company is a part of
Earnings_next_year The amount of money in dollars that the company earns in the following year
PRICE The price of the stock when it is bought
Sell The price of the stock when it is sold
VOLUME The total number of shares that the company holds
COGS The total amount the company paid as a cost directly related to the sale of products
INVESTMENT The total asset or item acquired with the goal of generating income or appreciation
RECIEVABLE The debts owed to a company by its customers for goods that have been delivered or used but not yet paid for
INVENTORY How much raw materials used in production as well as the goods produced that are available for sale
DEBTS How much money the company borrow from other parties
CPALTT01USM657N_PC1 The percentage change in CPI (measure of inflation)
GDP The monetary value of all finished goods and services made within a country
GDP_PC1 The percentage change in GDP
T10Y2Y Ten year treasury bonds minus two year treasury bonds
M1SL The total currency and other liquid instruments in a country’s economy
M1SL_PC1 The percentage change in money supply
Earnings_next_year How much profit that a company produces next year
PROFIT How much the money made or lost on an investment

Loading data

finalDATASET <- read_csv("FINALDATASET.csv")
macro_data <- read_csv("clean_macro - Sheet1.csv")

Data Preprocessing

For the data preprocessing, we combined our data sources and filled missing values for some fundamental factors by using the median of that value in the sector for a certain year. We also created sector dummies in this step. We then dropped some variables that are not in our interests and the splited the testing and trainning data.

final_data <- finalDATASET %>% 
  # make Sector dummy variables
  dummy_cols(select_columns = "Sector") %>% 
  # merge with updated macro data
  select(-CPALTT01USM657N_PC1,
         -GDP,
         -GDP_PC1,
         -M1SL_PC1 ,
         -M1SL,
         -PRICE,
         -Sell,
         -COMPANY) %>% 
  merge(macro_data) %>% 
  # convert Macro factors from characters to numeric
  mutate(across(c("CPALTT01USM657N_PC1","GDP","GDP_PC1","T10Y2Y","M1SL_PC1","M1SL"),
                as.numeric)) %>% 
  group_by(YEAR,Sector) %>% 
  # replacing the missing value with median od the industry in that year
  mutate(across(c(DEBTS,INVESTMENTS,CASH,VOLUME,EARNINGS,COGS,SALES,RECEIVABLE,INVENTORY),~replace(.,.==0,median(.)))) %>%
  # delete less important factors -> Exchange can possibily be deleted
  ungroup() %>%  
  mutate(across(c(!where(is.numeric),-"Name"),as.factor)) %>% 
  select(-Earnings_next_year,-observation_date) %>% 
  drop_na() 

# filter out the data for 2021
final_data_2021 <- final_data %>% 
  filter(YEAR == 2021)

final_data <- final_data %>% 
  filter(YEAR < 2021)

# split the data
set.seed(327) #for reproducibility

data_split <- initial_split(final_data, 
                             prop = .75)
data_training <- training(data_split)
data_testing <- testing(data_split)

# quick look of the data 
final_data %>% 
  head(5)

Data Visualization

We first explored distributions of our predictors and outcome. As the following graph suggested, all the numeric variables are severely right-skewed with some outliers. In our data, though the numbers of firms in each sector are unbalanced, we got a decent amount of data for each sector.

  final_data %>% 
  select(where(is.numeric)) %>% 
  select(-starts_with("Sector_")) %>% 
  pivot_longer(cols = everything(),
               names_to = "variable", 
               values_to = "value") %>% 
  ggplot(aes(x = value)) +
  geom_histogram(bins = 30) +
  facet_wrap(vars(variable), 
             scales = "free",
             nrow = 4,
             ncol=5)

final_data %>% 
  select(where(is.factor)) %>% 
  pivot_longer(cols = everything(),
               names_to = "variable", 
               values_to = "value") %>% 
  ggplot(aes(x = value)) +
  geom_bar() +
  facet_wrap(vars(variable), 
             scales = "free", 
             nrow = 4) + 
  theme(axis.text.x = element_text(angle = 45))

Then, we continued to explore the relationship between regressors and the outcome. By observation, we can’t see a strong correlation between fundemental factors, such as investments and debts, and stock return. It may indicate that linear model is not an ideal model in this case.

final_data %>% 
  ggplot(aes(x = INVESTMENTS, y = PROFIT,color = YEAR)) + 
  geom_point(alpha = 0.5)+
  geom_smooth(se = FALSE) + 
  labs(title = "Relationship between investments and stock return")

final_data %>% 
  ggplot(aes(x = DEBTS, y = PROFIT,color = YEAR)) + 
  geom_point(alpha = 0.5)+
  geom_smooth(se = FALSE) + 
  labs(title = "Relationship between debts and stock return")

With this anamiation, we could see that the returns of stocks follow a cycle, which may be influenced by macroeconomic condition. The return in 1999 and 2019 seems to be the highest for all industries. Also, we noticed that some industries vary a lot year to year, such as IT and healthcare industry.

sector_return_an<-final_data %>%
  ggplot(aes(x = PROFIT, y = Sector)) +
  geom_boxplot(aes(color = Sector),
             alpha = .8,
             size = 1) + 
  labs(title = "Spread of stock return in different sector",
       subtitle = "YEAR: {closest_state}",
       color = "") + 
  transition_states(YEAR)
  
animate(sector_return_an, duration = 25)
anim_save("sector_return.gif")
knitr::include_graphics("sector_return.gif")

We further explored the relationship between the industry and the potential macroeconomic influencer using graphs below. Though the relationship doesn’t seem to be linear, we do see how macroeconomic condition affect each industry differently and will account for that in our model.

final_data %>% 
  group_by(YEAR,Sector) %>% 
  mutate(profit_por = PROFIT*`MARKET CAP`/(sum(`MARKET CAP`,na.rm = TRUE))) %>% 
  summarise(Sector_profit = sum(profit_por),
            GDP_PC1 = mean(GDP_PC1),
            CPALTT01USM657N_PC1 = mean(CPALTT01USM657N_PC1),
            Sector = Sector[1]) %>% 
  ggplot(aes(x = CPALTT01USM657N_PC1,y = Sector_profit,color = Sector))+
  geom_smooth(se = FALSE) + 
  labs(title = "Relationship between inflation and average return of a industry",
       x = "Percent Change in CPI",
       y = "Average Return of a sector")

final_data %>% 
  group_by(YEAR,Sector) %>% 
  mutate(profit_por = PROFIT*`MARKET CAP`/(sum(`MARKET CAP`,na.rm = TRUE))) %>% 
  summarise(Sector_profit = sum(profit_por),
            GDP_PC1 = mean(GDP_PC1),
            CPALTT01USM657N_PC1 = mean(CPALTT01USM657N_PC1),
            Sector = Sector[1]) %>% 
  ggplot(aes(x = GDP_PC1,y = Sector_profit,color = Sector))+
  geom_smooth(se = FALSE) + 
  labs(title = "Relationship between GDP  and average return of a industry",
       x = "Percent Change in GDP",
       y = "Average Return of a sector")

Lasso Model

Building Recipe

In the lasso model, to account for the fact the macroeconomic condition affects each industry differently, we also included the interaction term between GDP and sector dummies.

return_recipe <- recipe(PROFIT ~ ., #short-cut, . = all other vars
                       data = data_training) %>% 
  # filter to only have data after 2020
  step_filter(YEAR<2021) %>% 
  step_rm(Name,Sector,YEAR) %>% 
  #step_rm(GDP,M1SL,Name,Sector,YEAR) %>% 
  # add PE 
  step_mutate(PE = `MARKET CAP`/EARNINGS) %>%
  # Normalize all variables except for GDP
  step_normalize(all_predictors(), 
                 -all_nominal(),
                 -starts_with("Sector_")) %>% 
  # Create interaction terms
  step_interact(terms = ~c(GDP_PC1):starts_with("Sector_")) 
# show the data in recipe
return_recipe %>% 
  prep(data_training) %>%
  # using bake(new_data = NULL) gives same result as juice()
  # bake(new_data = NULL)
  juice() 

Select tuning parameter

return_linear_mod <- 
  # Define a lasso model 
  # I believe default is mixture = 1 so probably don't need 
  linear_reg(mixture = 1) %>% 
  # Set the engine to "glmnet" 
  set_engine("glmnet") %>% 
  # The parameters we will tune.
  set_args(penalty = tune()) %>% 
  # Use "regression"
  set_mode("regression")

set.seed(456)
return_lm_wf <- 
  # Set up the workflow
  workflow() %>% 
  # Add the recipe
  add_recipe(return_recipe) %>% 
  # Add the modeling
  add_model(return_linear_mod)

penalty_grid <- grid_regular(penalty(),
                             levels = 10)

return_cv <- vfold_cv(data_training, v = 5)

return_lm_tune <- 
  return_lm_wf %>% 
  tune_grid(
    resamples = return_cv,
    grid = penalty_grid
    )

best_param<-return_lm_tune %>% 
  select_best(metric = "rmse")

return_lasso_final_wf <- return_lm_wf %>% 
  finalize_workflow(best_param)

return_lasso_final_mod <- return_lasso_final_wf %>% 
  fit(data = data_training)

# visulization for best param
set.seed(456)
return_lm_tune %>% 
  collect_metrics() %>% 
  filter(.metric == "rmse") %>% 
  ggplot(aes(x = penalty, y = mean)) +
  geom_point() +
  geom_line() +
  scale_x_log10(
   breaks = scales::trans_breaks("log10", function(x) 10^x),
   labels = scales::trans_format("log10",scales::math_format(10^.x))) +
  labs(x = "penalty", y = "rmse")

Lasso results

The table below shows the estimate of each predictor in the Lasso Model. We can see that indicators in macroeconomics are relatively important to predict the stock return. The change in inflation and GDP all remain significant after shrinking. The interaction terms between GDP and industries also showed importance, accounting for the fact that the macroeconomic condition affects each industry differently: GDP seems to affect the stock return in Communication Services, Energy, and Consumer Discretionary sectors less.

return_lasso_final_mod %>% 
  pull_workflow_fit() %>% 
  tidy() %>% 
  arrange(desc(estimate)) %>% 
  kbl() %>% 
  kable_styling(bootstrap_options = c("striped", "bordered", "hover", "condensed")) %>% 
  scroll_box(width = "100%", height = "500px")
term estimate penalty
M1SL 52.2489802 0.0059948
CPALTT01USM657N_PC1 22.8362276 0.0059948
GDP_PC1 18.5139718 0.0059948
(Intercept) 17.4206737 0.0059948
GDP_PC1_x_Sector_Utilities 14.5169194 0.0059948
GDP_PC1_x_Sector_Consumer Staples 10.9623359 0.0059948
GDP_PC1_x_Sector_Health Care 10.0994574 0.0059948
Sector_Information Technology 9.1062940 0.0059948
T10Y2Y 8.3785234 0.0059948
GDP_PC1_x_Sector_Real Estate 7.9131832 0.0059948
Sector_Communication Services 7.4629557 0.0059948
Sector_Health Care 5.9942110 0.0059948
Sector_Consumer Discretionary 4.8412271 0.0059948
GDP_PC1_x_Sector_Information Technology 4.0219608 0.0059948
Sector_Energy 2.7587880 0.0059948
COGS 2.2012752 0.0059948
GDP_PC1_x_Sector_Industrials 2.0052201 0.0059948
VOLUME 0.9051955 0.0059948
INVESTMENTS 0.4845378 0.0059948
CASH 0.4031170 0.0059948
EARNINGS 0.3671604 0.0059948
RECEIVABLE 0.1688682 0.0059948
INVENTORY 0.1677270 0.0059948
GDP_PC1_x_Sector_Financials 0.0413482 0.0059948
Sector_Materials 0.0000000 0.0059948
PE -0.2304146 0.0059948
DEBTS -0.2444685 0.0059948
Sector_Industrials -0.4250661 0.0059948
GDP_PC1_x_Sector_Materials -1.0239217 0.0059948
GDP_PC1_x_Sector_Communication Services -1.4192763 0.0059948
Sector_Financials -1.5534082 0.0059948
SALES -3.2886701 0.0059948
Sector_Real Estate -3.3504621 0.0059948
MARKET CAP -3.8678288 0.0059948
Sector_Consumer Staples -5.4154183 0.0059948
GDP_PC1_x_Sector_Energy -5.8821361 0.0059948
GDP_PC1_x_Sector_Consumer Discretionary -8.6297519 0.0059948
Sector_Utilities -8.7405789 0.0059948
M1SL_PC1 -11.4973966 0.0059948
GDP -16.0930330 0.0059948

Model Evaluation

Prediciton precision

The Lasso model does not perform well. It only explains 20% of the varaince in stock return and it also has a relatively high rmse of around 46.

return_lm_tune %>%
  select(id, .metrics) %>%
  unnest(.metrics) %>%
  filter(.metric == "rsq") %>% 
  filter(.config == "Preprocessor1_Model08") %>% 
  summarise(mean_rsq = mean(.estimate))
return_lm_tune %>%
  select(id, .metrics) %>%
  unnest(.metrics) %>%
  filter(.metric == "rmse") %>% 
  filter(.config == "Preprocessor1_Model08") %>% 
  summarise(mean_rmse = mean(.estimate))
set.seed(456)
prediction <- predict(
  return_lasso_final_mod,
  new_data = data_training)

training_pred<-data_training %>% 
  mutate(.pred = prediction$.pred)
  
  
training_pred %>% 
  ggplot(aes(x = PROFIT, 
             y = .pred,
             color = YEAR)) +
  geom_point(alpha = .5, 
             size = .5) +
  geom_smooth(se = FALSE) +
  geom_abline(slope = 1, 
              intercept = 0, 
              color = "darkred") +
  geom_text(aes(label = Name),label.size = 0.15,data = training_pred %>% filter(PROFIT>400) )+
  labs(x = "Actual Return", 
       y = "Predicted Return") +
  scale_color_viridis_b()

Overfitting

Below shows the r-square and rmse on testing data. They are very similar to those on trainning data so we would safely conclude that our model did not overfit.

return_lasso_test <- return_lasso_final_wf %>% 
  last_fit(data_split)

# Metrics for model applied to test data
return_lasso_test %>% 
  collect_metrics()

The following graphs show the performance of the model on the testing data:

test_prediction <- predict(
  return_lasso_final_mod,
  new_data = data_testing)

testing_lasso_pred<-data_testing %>% 
  mutate(.pred = test_prediction$.pred)
  
  
testing_lasso_pred %>% 
  ggplot(aes(x = PROFIT, 
             y = .pred,
             color = YEAR)) +
  geom_point(alpha = .5, 
             size = .5) +
  geom_smooth(se = FALSE) +
  geom_abline(slope = 1, 
              intercept = 0, 
              color = "darkred") +
  geom_text(aes(label = Name),label.size = 0.15,data = testing_lasso_pred %>% filter(PROFIT>300) )+
  labs(x = "Actual Return", 
       y = "Predicted Return") + 
  scale_color_viridis_b()

Interpretable Machine Learning

Again, the boxplot and the histogram of residuals, the model does not predict the return well and this may because some extreme values.

lasso_explain <- 
  explain_tidymodels(
    model = return_lasso_final_mod,
    data = data_training %>% select(-PROFIT), 
    y = data_training %>%  pull(PROFIT),
    label = "lasso"
  )
## Preparation of a new explainer is initiated
##   -> model label       :  lasso 
##   -> data              :  7172  rows  30  cols 
##   -> data              :  tibble converted into a data.frame 
##   -> target variable   :  7172  values 
##   -> predict function  :  yhat.workflow  will be used (  default  )
##   -> predicted values  :  No value for predict function target column. (  default  )
##   -> model_info        :  package tidymodels , ver. 0.1.3 , task regression (  default  ) 
##   -> predicted values  :  numerical, min =  -49.61277 , mean =  19.22262 , max =  121.2461  
##   -> residual function :  difference between y and yhat (  default  )
##   -> residuals         :  numerical, min =  -146.2719 , mean =  -4.401566e-14 , max =  1002.161  
##   A new explainer has been created! 
lasso_mod_perf <- model_performance(lasso_explain)
hist_plot <- 
  plot(lasso_mod_perf,
       geom = "histogram")
box_plot <-
  plot(lasso_mod_perf,
       geom = "boxplot")

hist_plot

box_plot

Below shows the feature importance plots generated by two different methods. We could see that macroeconomic indicators remained importance in both of the plots but market cap seems to be an crucial factor when we use permutation method.

set.seed(10) #since we are sampling & permuting, we set a seed so we can replicate the results
lasso_var_imp <- 
  model_parts(
    lasso_explain
    )

plot(lasso_var_imp, show_boxplots = TRUE)

return_lasso_final_mod %>% 
  pull_workflow_fit() %>% 
  vip()

Random Forest Model

Building Random Forest model

To build the random forest model, we set up recipe using our training data (data_training), define model with mtry = 6, min_n = 10 and numbers of tree = 200, create ranger workflow, and then we can fit the model.

# set up recipe 
ranger_recipe <-
  recipe(PROFIT ~ ., #short-cut, . = all other vars
                       data = data_training) %>%
  step_filter(YEAR<2021) %>% 
  # remove the unwanted variables
  step_rm(YEAR,Name,GDP,M1SL,Sector) %>% 
  # add PE 
  step_mutate(PE = `MARKET CAP`/EARNINGS)

#define model
ranger_spec <- 
  rand_forest(mtry = 6, 
              min_n = 10, 
              trees = 200) %>% 
  set_mode("regression") %>% 
  set_engine("ranger")

#create workflow
ranger_workflow <- 
  workflow() %>% 
  add_recipe(ranger_recipe) %>% 
  add_model(ranger_spec)

#fit the model
set.seed(712) # for reproducibility - random sampling in random forest choosing number of variables
ranger_fit <- ranger_workflow %>% 
  fit(data_training)

Random Forest results

The table below shows the OOB error (MSE), OOB RMSE (Root mean square error), and R squared of the stock return predictions using the random forest model above.

OOB error:

ranger_fit$fit$fit$fit$prediction.error
## [1] 1545.135

OOB RMSE:

sqrt(ranger_fit$fit$fit$fit$prediction.error)
## [1] 39.3082

R Squared:

ranger_fit$fit$fit$fit$r.squared
## [1] 0.4086307

Model Evaluation

Prediciton precision (Traning data)

Even though the mean rmse from the random forest (39.30787) seems to be fairly high but it is still lower than the LASSO model of 46. Thus, random forest model performs better.

set.seed(1211) # for reproducibility
data_cv <- vfold_cv(data_training, v = 5)

metric <- metric_set(rmse)
ctrl_res <- control_stack_resamples()

ranger_cv <- ranger_workflow %>% 
  fit_resamples(data_cv, 
                metrics = metric,
                control = ctrl_res)

# Evaluation metrics averaged over all folds:
collect_metrics(ranger_cv)

We also plot a graph showing the actual return vs predicted return.

ranger_prediction <- predict(
  ranger_fit,
  new_data = data_training)

ranger_training_pred<-data_training %>% 
  mutate(.pred = ranger_prediction$.pred)
  
  
ranger_training_pred %>%
  ggplot(aes(x = PROFIT,
             y = .pred)) +
  geom_point(alpha = .5,
             size = .5) +
  geom_smooth(se = FALSE) +
  geom_abline(slope = 1,
              intercept = 0,
              color = "darkred") +
  labs(x = "Actual Return",
       y = "Predicted Return")

Prediciton precision (Testing data)

The table below shows the rmse on testing data. It is a bit higher but fairly similar to the error on training data so we could say that our model did not overfit.

set.seed(1211) # for reproducibility
data_cv <- vfold_cv(data_testing, v = 5)

metric <- metric_set(rmse)
ctrl_res <- control_stack_resamples()

ranger_cv <- ranger_workflow %>% 
  fit_resamples(data_cv, 
                metrics = metric,
                control = ctrl_res)

# Evaluation metrics averaged over all folds:
collect_metrics(ranger_cv)

Below is the graph showing the actual return vs. predicted return on testing data.

ranger_test_prediction <- predict(
  ranger_fit,
  new_data = data_testing)

ranger_test_pred<-data_testing %>% 
  mutate(.pred = ranger_test_prediction$.pred)
  
  
ranger_test_pred %>%
  ggplot(aes(x = PROFIT,
             y = .pred)) +
  geom_point(alpha = .5,
             size = .5) +
  geom_smooth(se = FALSE) +
  geom_abline(slope = 1,
              intercept = 0,
              color = "darkred") +
  labs(x = "Actual Return",
       y = "Predicted Return")

Interpretable Machine Learning

Based on the box-plot and the histogram below, the residuals mostly lie between -50 to 50. But there are some a few outliners that can go up to 400.

rf_explain <- 
  explain_tidymodels(
    model = ranger_fit,
    data = data_training %>% select(-PROFIT), 
    y = data_training %>%  pull(PROFIT),
    label = "rf"
  )
## Preparation of a new explainer is initiated
##   -> model label       :  rf 
##   -> data              :  7172  rows  30  cols 
##   -> data              :  tibble converted into a data.frame 
##   -> target variable   :  7172  values 
##   -> predict function  :  yhat.workflow  will be used (  default  )
##   -> predicted values  :  No value for predict function target column. (  default  )
##   -> model_info        :  package tidymodels , ver. 0.1.3 , task regression (  default  ) 
##   -> predicted values  :  numerical, min =  -76.57776 , mean =  19.57231 , max =  438.0276  
##   -> residual function :  difference between y and yhat (  default  )
##   -> residuals         :  numerical, min =  -101.4695 , mean =  -0.3496883 , max =  599.5611  
##   A new explainer has been created! 
rf_mod_perf <-  model_performance(rf_explain)

hist_plot <- 
  plot(rf_mod_perf, 
       geom = "histogram")
box_plot <-
  plot(rf_mod_perf, 
       geom = "boxplot")

hist_plot

box_plot

According to the feature importance bar chart below, we can see that the top three important features are market cap, earnings, M1SL_PC1, and GDP_PC1.

set.seed(10) #since we are sampling & permuting, we set a seed so we can replicate the results
rf_var_imp <- 
  model_parts(
    rf_explain
    )
plot(rf_var_imp, show_boxplots = TRUE)

After creating two models, we then move on to the final method: stacking. For the stacking model, we also add one more model: KNN to improve the accuracy rate of the model.

Stacking Model

Random Forest Model

ranger_recipe <- 
  recipe(formula = PROFIT ~ ., 
         data = data_training) %>% 
  # Make these evaluative variables, not included in modeling
  update_role(all_of(c("YEAR", "Name", "Sector")),
              new_role = "evaluative")

ranger_spec <- 
  rand_forest(mtry = 6, 
              min_n = 10, 
              trees = 200) %>% 
  set_mode("regression") %>% 
  set_engine("ranger")

ranger_workflow <- 
  workflow() %>% 
  add_recipe(ranger_recipe) %>% 
  add_model(ranger_spec) 

ranger_fit <- ranger_workflow %>% 
  fit(data_training)

set.seed(1211) # for reproducibility
final_data_cv <- vfold_cv(data_training, v = 5)

metric <- metric_set(rmse)
ctrl_res <- control_stack_resamples()

ranger_cv <- ranger_workflow %>% 
  fit_resamples(final_data_cv, 
                metrics = metric,
                control = ctrl_res)

LASSO Model

# lasso recipe and transformation steps
lasso_final_data_recipe <- recipe(PROFIT ~ ., 
                       data = data_training) %>% 
  #step_rm(Name, Sector, YEAR, COMPANY) %>%
  update_role(all_of(c("Name",
                       "Sector",
                       "YEAR")),
              new_role = "evaluative") %>% 
  step_dummy(all_nominal(), 
             -all_outcomes(), 
             -has_role(match = "evaluative")) %>% 
  step_normalize(all_predictors(), 
                 -all_nominal())

#define lasso model
lasso_mod <- 
  linear_reg(mixture = 1) %>% 
  set_engine("glmnet") %>% 
  set_args(penalty = tune()) %>% 
  set_mode("regression")

# create workflow
lasso_wf <- 
  workflow() %>% 
  add_recipe(lasso_final_data_recipe) %>% 
  add_model(lasso_mod)

# penalty grid - changed to 10 levels
penalty_grid <- grid_regular(penalty(),
                             levels = 10)

# add ctrl_grid - assures predictions and workflows are saved
ctrl_grid <- control_stack_grid()

# tune the model using the same cv samples as random forest

lasso_tune <- 
  lasso_wf %>% 
  tune_grid(
    resamples = final_data_cv,
    grid = penalty_grid,
    metrics = metric,
    control = ctrl_grid
    )

KNN Model

# create a model definition
knn_mod <-
  nearest_neighbor(
    neighbors = tune("k")
  ) %>%
  set_engine("kknn") %>% 
  set_mode("regression")

# create the workflow
knn_wf <- 
  workflow() %>% 
  add_model(knn_mod) %>%
  add_recipe(lasso_final_data_recipe)

# tune it using 4 tuning parameters
knn_tune <- 
  knn_wf %>% 
  tune_grid(
    final_data_cv,
    metrics = metric,
    grid = 4,
    control = ctrl_grid
  )

Stacking all three models

final_data_stack <- 
  stacks() %>% 
  add_candidates(ranger_cv) %>% 
  add_candidates(lasso_tune) %>% 
  add_candidates(knn_tune)
final_data_blend <- 
  final_data_stack %>% 
  blend_predictions()
final_data_blend
## # A tibble: 2 x 3
##   member        type             weight
##   <chr>         <chr>             <dbl>
## 1 ranger_cv_1_1 rand_forest       0.835
## 2 knn_tune_1_4  nearest_neighbor  0.254
final_data_blend$metrics %>% 
  filter(.metric == "rmse") %>% 
  summarise(mean_rmse = mean(mean))
autoplot(final_data_blend)

final_data_final_stack <- final_data_blend %>% 
  fit_members()
final_data_final_stack %>% 
  predict(new_data = data_testing) %>% 
  bind_cols(data_testing) %>% 
  select(Name, .pred, PROFIT) %>% 
  arrange(desc(.pred)) %>% 
  head(10)

Here, we can see that when the model predicts company with high rate of return, the model performs really well.

Comparison of the three models

With the three models, we then move on to see which model performs the best:

Lasso model

data_frame(return_lm_tune %>%
  select(id, .metrics) %>%
  unnest(.metrics) %>%
  filter(.metric == "rsq") %>% 
  filter(.config == "Preprocessor1_Model08") %>% 
  summarise(mean_rsq = mean(.estimate)),

return_lm_tune %>%
  select(id, .metrics) %>%
  unnest(.metrics) %>%
  filter(.metric == "rmse") %>% 
  filter(.config == "Preprocessor1_Model08") %>% 
  summarise(mean_rmse = mean(.estimate)))

Random Forest Model

#OOB RMSE
data.frame(mean_rmse = sqrt(ranger_fit$fit$fit$fit$prediction.error),
# R squared
mean_rsq = ranger_fit$fit$fit$fit$r.squared)

Stacking Model

# Stacking model: 
data.frame(final_data_blend$metrics %>% 
  filter(.metric == "rmse") %>% 
  summarise(mean_rmse = mean(mean)),

final_data_blend$metrics %>% 
  filter(.metric == "rsq") %>% 
  summarise(mean_rsq = mean(mean)))

Here, we can see that compared to the three models, even though Random Forest performs better than the stacking model, stacking model use features in the Random Forest along with additional features from KNN and lasso. With that reason, we will choose stacking model as our model choice.

Stock Return Prediction for 2021

After picking our model, we then move on to use the model to predict the potential profit for 2021:

set.seed(456)
ytd = c(80.30, 39.77, -8.26, 11.29, 22.08, 132.51, 137.04, -4.22, 39.77, 20.76, 26.41, 26.34, 41.50, 178.43, 18.70, 87.29, -6.16, 52.58, 8.70, -13.70)
set.seed(456)
pred_2021<-final_data_final_stack %>% 
  predict(new_data = final_data_2021) %>% 
  bind_cols(final_data_2021) %>% 
  select(Name, .pred) %>% 
  arrange(desc(.pred)) %>% 
  head(20) %>% 
  mutate(actual_ytd = ytd) 
pred_2021 %>% 
  kbl() %>% 
  kable_styling(bootstrap_options = c("striped", "bordered", "hover", "condensed")) %>% 
  column_spec(c(1,3), color = ifelse(pred_2021$actual_ytd < 0, "red", "darkgreen")) %>% 
  scroll_box(width = "100%", height = "500px") 
Name .pred actual_ytd
APA Corporation 67.67959 80.30
Marathon Oil 63.13998 39.77
News Corp (Class B) 63.06597 -8.26
Under Armour (Class C) 62.82090 11.29
DuPont 62.70736 22.08
Ford 62.55623 132.51
Norwegian Cruise Line Holdings 62.50470 137.04
Under Armour (Class A) 61.68342 -4.22
Carnival Corporation 61.52755 39.77
DXC Technology 59.89012 20.76
Halliburton 59.45987 26.41
Raytheon Technologies 58.35294 26.34
Devon Energy 57.31403 41.50
Schlumberger 57.14758 178.43
ConocoPhillips 56.75287 18.70
Baker Hughes 56.39542 87.29
Hess Corporation 56.39404 -6.16
Southwest Airlines 56.14967 52.58
Bristol Myers Squibb 56.04499 8.70
Boeing 55.79926 -13.70

Conclusion

The weakness of our model is that the average error of prediction is still very high so if one wants to predict the exact return, our model won’t be ideal. But the strength of our model is that for stocks with extremely high returns, even though the prediction might not be that precise, highly likely, our model will predict positive returns. That means, in real life, if we choose the top stocks to invest in based on our prediction, it is less likely we are going to lose money. Another strength of using a model to help with investment is that it excludes our subjective feelings.

To make the model better, we could do more research and add more regressors. For example, some financial indicators that are important for value investing are not reflected in our model due to the lack of data. Examples of those variables include Price to Sales, Price to Cash Flow, and Price to Book. Also, since we are concerned about the long-term return and all fundamental factors usually take longer to affect the firms, it’s probably helpful to do return in 2 years or 3 years or include lag of some variables in our model.

LS0tCnRpdGxlOiAiRmluYWwgUHJvamVjdCIKYXV0aG9yOiAiRHVjIE5nbywgUml0YSBMaXUsIFNpdmh1byBQcmFrIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdG9jOiBUUlVFCiAgICB0b2NfZmxvYXQ6IFRSVUUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdGhlbWU6IGpvdXJuYWwKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkgICAgICAgICMgZm9yIGRhdGEgY2xlYW5pbmcgYW5kIHBsb3R0aW5nCmxpYnJhcnkodGlkeW1vZGVscykgICAgICAgIyBmb3IgbW9kZWxpbmcgLi4uIHRpZGlseQpsaWJyYXJ5KGx1YnJpZGF0ZSkgICAgICAgICMgZm9yIGRhdGUgbWFuaXB1bGF0aW9uCmxpYnJhcnkob3BlbmludHJvKSAgICAgICAgIyBmb3IgdGhlIGFiYnIyc3RhdGUoKSBmdW5jdGlvbgpsaWJyYXJ5KGdwbG90cykgICAgICAgICAgICMgZm9yIGNvbDJoZXgoKSBmdW5jdGlvbgpsaWJyYXJ5KFJDb2xvckJyZXdlcikgICAgICMgZm9yIGNvbG9yIHBhbGV0dGVzCmxpYnJhcnkoZ2d0aGVtZXMpICAgICAgICAgIyBmb3IgbW9yZSB0aGVtZXMgKGluY2x1ZGluZyB0aGVtZV9tYXAoKSkKbGlicmFyeShwbG90bHkpICAgICAgICAgICAjIGZvciB0aGUgZ2dwbG90bHkoKSAtIGJhc2ljIGludGVyYWN0aXZpdHkKbGlicmFyeShnZ2FuaW1hdGUpICAgICAgICAjIGZvciBhZGRpbmcgYW5pbWF0aW9uIGxheWVycyB0byBnZ3Bsb3RzCmxpYnJhcnkodHJhbnNmb3JtcikgICAgICAgIyBmb3IgInR3ZWVuaW5nIiAoZ2dhbmltYXRlKQpsaWJyYXJ5KGdpZnNraSkgICAgICAgICAgICMgbmVlZCB0aGUgbGlicmFyeSBmb3IgY3JlYXRpbmcgZ2lmcyBidXQgZG9uJ3QgbmVlZCB0byBsb2FkIGVhY2ggdGltZQpsaWJyYXJ5KHNoaW55KSAgICAgICAgICAgICMgZm9yIGNyZWF0aW5nIGludGVyYWN0aXZlIGFwcHMKbGlicmFyeShqYW5pdG9yKSAgCmxpYnJhcnkoc3RhY2tzKSAgICAgICAgICAgICMgZm9yIHN0YWNraW5nIG1vZGVscwpsaWJyYXJ5KGdsbW5ldCkgICAgICAgICAgICAjIGZvciByZWd1bGFyaXplZCByZWdyZXNzaW9uLCBpbmNsdWRpbmcgTEFTU08KbGlicmFyeShyYW5nZXIpICAgICAgICAgICAgIyBmb3IgcmFuZG9tIGZvcmVzdCBtb2RlbApsaWJyYXJ5KGtrbm4pICAgICAgICAgICAgICAjIGZvciBrbm4gbW9kZWwKbGlicmFyeShuYW5pYXIpICAgICAgICAgICAgIyBmb3IgZXhhbWluaW5nIG1pc3NpbmcgdmFsdWVzIChOQXMpCmxpYnJhcnkobW9kZXJuZGl2ZSkgICAgICAgICMgZm9yIEtpbmcgQ291bnR5IGhvdXNpbmcgZGF0YQpsaWJyYXJ5KHZpcCkgICAgICAgICAgICAgICAjIGZvciB2YXJpYWJsZSBpbXBvcnRhbmNlIHBsb3RzCmxpYnJhcnkocm1hcmtkb3duKSAgICAgICAgICMgZm9yIHBhZ2VkIHRhYmxlcwpsaWJyYXJ5KGZhc3REdW1taWVzKQpsaWJyYXJ5KHVzZW1vZGVscykgICAgICAgICAjIGZvciBzdWdnZXN0aW5nIHN0ZXBfWFhYKCkgZnVuY3Rpb25zCmxpYnJhcnkocmVhZHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeShEQUxFWCkgICAgICAgICAgICAgIyBtb2RlbCBBZ25vc3RpYyBMYW5ndWFnZSBmb3IgZXhwbG9yYXRpb24gYW5kIGV4cGxhbmF0aW9uIChmb3IgbW9kZWwgaW50ZXJwcmV0YXRpb24pICAKbGlicmFyeShEQUxFWHRyYSkKdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkKYGBgCgoKIyMgSW50cm9kdWN0aW9uIAoKVGhlIGZpbmFuY2lhbCBtYXJrZXQgaXMgYSBzdHJhbmdlIHBsYWNlIHRoYXQgaXMgdmVyeSBoYXJkIHRvIG5hdmlnYXRlIGFyb3VuZC4gV2UgaGF2ZSBzZWVuIFdhcnJlbiBCdWZmZXR0LCBSYXkgRGFsaW8sIENoYXJsaWUgTXVuZ2VyIC0gdGhlIHZlcnkgdmVyeSBiZXN0IHdobyBoYXZlIGhhZCBiaWxsaW9ucyBvZiBkb2xsYXJzLiBPdGhlcnMsIDk1JSBvZiB0aGUgcG9wdWxhdGlvbiwgbG9zZSB0aGUgbW9uZXkgaW5zdGVhZC4gCgpTbywgaG93IGFyZSB0aGUgYmVzdCBvZiB0aGUgYmVzdCBwaWNrIGl0cyBzdG9ja3M/IEl0IGlzIGZyb20gdGhlIGZ1bmRhbWVudGFsLCB0aGUgdGVjaG5pY2FsIHNpZGUgb3IgdGhlIHNlbnRpbWVudGFsIHNpZGU/IFdpdGhpbiB0aGlzIHBhcGVyLCB3ZSBob3BlIHRvIGJyaW5nIGFub3RoZXIgcGVyc3BlY3RpdmUsIHVzaW5nIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIHRvIHByZWRpY3QgdGhlIHByb2ZpdGFiaWxpdHkgb2YgdGhlIHN0b2NrIHByaWNlLiAKCiMjIyBQYXBlciBPdXRsaW5lIAoKRmlyc3QsIHdlIHdpbGwgbGlzdCBhbmQgZXhwbGFpbiB0aGUgZGVmaW5pdGlvbiBvZiBlYWNoIHZhcmlhYmxlIGluIG91ciBkYXRhc2V0LiBUaGVyZSBhcmUgMjUgdmFyaWFibGVzIGluIHRvdGFsIGFzIGxpc3RlZCBiZWxvdy4gVGhlIHZhcmlhYmxlIHdlIGFyZSBnb2luZyB0byBwcmVkaWN0IGlzIGBQUk9GSVRgLiBUaGVuLCB3ZSB3aWxsIHN0YXJ0IHRvIHByb2Nlc3Mgb3VyIGRhdGEgYnkgbWVyZ2luZyB3aXRoIHRoZSBtYWNybyBkYXRhIGFuZCByZW1vdmluZyBzb21lIHVud2FudGVkIHZhcmlhYmxlcy4gQWZ0ZXIgcHJvY2Vzc2luZyB0aGUgZGF0YSwgd2Ugd2lsbCBoYXZlIGEgZGF0YSB2aXN1YWxpemF0aW9uIHNlY3Rpb24gdGhhdCBzaG93cyB0aGUgZGlzdHJpYnV0aW9uIGFuZCByZWxhdGlvbnNoaXAgYmV0d2VlbiBzb21lIHZhcmlhYmxlcy4gQW5kIHRvIGZpbmQgdGhlIGJlc3QgbW9kZWwsIHdlIGNob29zZSB0byBleHBsb3JlIHRocmVlIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIHdoaWNoIGFyZSBMQVNTTywgUmFuZG9tIGZvcmVzdCBhbmQgU3RhY2tpbmcgbW9kZWxzLiBXZSB1c2UgYSBzdGFja2luZyBtZXRob2QgdG8gY3JlYXRlIHRoZSBzdGFja2luZyBtb2RlbCBieSBjb21iaW5pbmcgdGhyZWUgbW9kZWxzIChMYXNzbywgUmFuZG9tIEZvcmVzdCwgYW5kIEtOTikuIExhc3RseSwgd2Ugd2lsbCBtYWtlIGEgc3RvY2sgcmV0dXJuIHByZWRpY3Rpb24gZm9yIDIwMjEgdXNpbmcgdGhlIHRyYWluZWQgbW9kZWwgd2l0aCB0aGUgbG93ZXN0IFItc3F1YXJlZCBhbmQgUk1TRS4gCgojIyMgTGlzdCBvZiBWYXJpYWJsZXMgCgpGb3IgdGhlIGRhdGFzZXQsIHdlIGluY2x1ZGVzIGZpbmFuY2lhbCBpbmZvcm1hdGlvbiBvbiBjb21wYW5pZXMgaW4gdGhlIFMmUCA1MDAgc3RvY2sgaW5kZXggZnJvbSAxOTk5LTIwMjEuIFRoaXMgaW5mb3JtYXRpb24gd2FzIHNjcmFwZWQgZnJvbSBZYWhvbyBGaW5hbmNlIGluIE5vdmVtYmVyIG9mIDIwMjEsIGFuZCBjb2xsZWN0ZWQgaW4gYSBjc3YgZm9ybWF0IGZvciBkYXRhIGFuYWx5c2lzLiBUaGUgaW5mb3JtYXRpb24gaW5jbHVkZXMgbWV0cmljcyBsaWtlIHNhbGVzLCBlYXJuaW5ncywgY29ncywgc3RvY2sgcHJpY2UsIGFuZCBtYXJrZXQgc2VjdG9yIGFzIHdlbGwgYXMgbWFjcm9lY29ub21pYyBkYXRhIHN1Y2ggYXMgR0RQIG9yIE1vbmV5IFN1cHBseS4gVGhlIGdvYWwgaXMgdG8gYW5hbHl6ZSBhbmQgbW9kZWwgdGhpcyBkYXRhIHRvIGJldHRlciBpbXByb3ZlIHByb2plY3Rpb25zIGZvciBhIGNvbXBhbnnigJlzIGZ1dHVyZSBwcm9maXRhYmlsaXR5LiBUaGUgdmFyaWFibGVzIGluIHRoZSBkYXRhIHNldCBhcmUgZGVzY3JpYmVkIGJlbG93OgoKfCBWYXJpYWJsZSAgICAgICAgICAgIHwgTWVhbmluZyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18CnwgWUVBUiAgICAgICAgICAgICAgICB8IFRoZSBmaW5hbmNpYWwgeWVhciBvZiB0aGUgY29tcGFueSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBDT01QQU5ZICAgICAgICAgICAgIHwgVGhlIGNvbXBhbnnigJlzIHN0b2NrIGFiYnJldmlhdGlvbiBzeW1ib2wgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgTUFSS0VULkNBUCAgICAgICAgICB8IFRoZSB0b3RhbCBtYXJrZXQgY2FwaXRhbGl6YXRpb24gb2YgdGhlIGNvbXBhbnkgKFZvbHVtZSAqIFByaWNlKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBFQVJOSU5HUyAgICAgICAgICAgIHwgVGhlIGVhcm5pbmdzIGluIGRvbGxhcnMgZm9yIHRoZSBwcmV2aW91cyB5ZWFyIGZvciB0aGUgZ2l2ZW4gY29tcGFueSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IFNBTEVTICAgICAgICAgICAgICAgfCBIb3cgbXVjaCB0aGUgY29tcGFueSBzb2xkIGluIGRvbGxhcnMgbGFzdCB5ZWFyICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgQ0FTSCAgICAgICAgICAgICAgICB8IEhvdyBtdWNoIGNhc2ggdGhlIGNvbXBhbnkgaGFzIGluIGRvbGxhcnMgYXQgdGhlIGVuZCBvZiB0aGUgcHJldmlvdXMgeWVhciAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBOYW1lICAgICAgICAgICAgICAgIHwgVGhlIGZ1bGwgbmFtZSBvZiB0aGUgY29tcGFueSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IFNlY3RvciAgICAgICAgICAgICAgfCBUaGUgbmFtZSBvZiB0aGUgc2VjdG9yIHRoYXQgdGhlIGNvbXBhbnkgaXMgYSBwYXJ0IG9mICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgRWFybmluZ3NfbmV4dF95ZWFyICB8IFRoZSBhbW91bnQgb2YgbW9uZXkgaW4gZG9sbGFycyB0aGF0IHRoZSBjb21wYW55IGVhcm5zIGluIHRoZSBmb2xsb3dpbmcgeWVhciAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBQUklDRSAgICAgICAgICAgICAgIHwgVGhlIHByaWNlIG9mIHRoZSBzdG9jayB3aGVuIGl0IGlzIGJvdWdodCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAKfCBTZWxsICAgICAgICAgICAgICAgIHwgVGhlIHByaWNlIG9mIHRoZSBzdG9jayB3aGVuIGl0IGlzIHNvbGQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IFZPTFVNRSAgICAgICAgICAgICAgfCBUaGUgdG90YWwgbnVtYmVyIG9mIHNoYXJlcyB0aGF0IHRoZSBjb21wYW55IGhvbGRzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgQ09HUyAgICAgICAgICAgICAgICB8IFRoZSB0b3RhbCBhbW91bnQgdGhlIGNvbXBhbnkgcGFpZCBhcyBhIGNvc3QgZGlyZWN0bHkgcmVsYXRlZCB0byB0aGUgc2FsZSBvZiBwcm9kdWN0cyAgICAgICAgICAgICAgICAgICAgICAgIHwgCnwgSU5WRVNUTUVOVCAgICAgICAgICB8IFRoZSB0b3RhbCBhc3NldCBvciBpdGVtIGFjcXVpcmVkIHdpdGggdGhlIGdvYWwgb2YgZ2VuZXJhdGluZyBpbmNvbWUgb3IgYXBwcmVjaWF0aW9uICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBSRUNJRVZBQkxFICAgICAgICAgIHwgVGhlIGRlYnRzIG93ZWQgdG8gYSBjb21wYW55IGJ5IGl0cyBjdXN0b21lcnMgZm9yIGdvb2RzIHRoYXQgaGF2ZSBiZWVuIGRlbGl2ZXJlZCBvciB1c2VkIGJ1dCBub3QgeWV0IHBhaWQgZm9yfCAKfCBJTlZFTlRPUlkgICAgICAgICAgIHwgSG93IG11Y2ggcmF3IG1hdGVyaWFscyB1c2VkIGluIHByb2R1Y3Rpb24gYXMgd2VsbCBhcyB0aGUgZ29vZHMgcHJvZHVjZWQgdGhhdCBhcmUgYXZhaWxhYmxlIGZvciBzYWxlICAgICAgICAgfCAKfCBERUJUUyAgICAgICAgICAgICAgIHwgSG93IG11Y2ggbW9uZXkgdGhlIGNvbXBhbnkgYm9ycm93IGZyb20gb3RoZXIgcGFydGllcyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAKfCBDUEFMVFQwMVVTTTY1N05fUEMxIHwgVGhlIHBlcmNlbnRhZ2UgY2hhbmdlIGluIENQSSAobWVhc3VyZSBvZiBpbmZsYXRpb24pICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAKfCBHRFAgICAgICAgICAgICAgICAgIHwgVGhlIG1vbmV0YXJ5IHZhbHVlIG9mIGFsbCBmaW5pc2hlZCBnb29kcyBhbmQgc2VydmljZXMgbWFkZSB3aXRoaW4gYSBjb3VudHJ5ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IEdEUF9QQzEgICAgICAgICAgICAgfCBUaGUgcGVyY2VudGFnZSBjaGFuZ2UgaW4gR0RQICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgVDEwWTJZICAgICAgICAgICAgICB8IFRlbiB5ZWFyIHRyZWFzdXJ5IGJvbmRzIG1pbnVzIHR3byB5ZWFyIHRyZWFzdXJ5IGJvbmRzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBNMVNMICAgICAgICAgICAgICAgIHwgVGhlIHRvdGFsIGN1cnJlbmN5IGFuZCBvdGhlciBsaXF1aWQgaW5zdHJ1bWVudHMgaW4gYSBjb3VudHJ5J3MgZWNvbm9teSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IE0xU0xfUEMxICAgICAgICAgICAgfCBUaGUgcGVyY2VudGFnZSBjaGFuZ2UgaW4gbW9uZXkgc3VwcGx5ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgRWFybmluZ3NfbmV4dF95ZWFyICB8IEhvdyBtdWNoIHByb2ZpdCB0aGF0IGEgY29tcGFueSBwcm9kdWNlcyBuZXh0IHllYXIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgCnwgUFJPRklUICAgICAgICAgICAgICB8IEhvdyBtdWNoIHRoZSBtb25leSBtYWRlIG9yIGxvc3Qgb24gYW4gaW52ZXN0bWVudCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKCiMjIyBMb2FkaW5nIGRhdGEKCmBgYHtyfQpmaW5hbERBVEFTRVQgPC0gcmVhZF9jc3YoIkZJTkFMREFUQVNFVC5jc3YiKQptYWNyb19kYXRhIDwtIHJlYWRfY3N2KCJjbGVhbl9tYWNybyAtIFNoZWV0MS5jc3YiKQpgYGAKCiMjIERhdGEgUHJlcHJvY2Vzc2luZyAKRm9yIHRoZSBkYXRhIHByZXByb2Nlc3NpbmcsIHdlIGNvbWJpbmVkIG91ciBkYXRhIHNvdXJjZXMgYW5kIGZpbGxlZCBtaXNzaW5nIHZhbHVlcyBmb3Igc29tZSBmdW5kYW1lbnRhbCBmYWN0b3JzIGJ5IHVzaW5nIHRoZSBtZWRpYW4gb2YgdGhhdCB2YWx1ZSBpbiB0aGUgc2VjdG9yIGZvciBhIGNlcnRhaW4geWVhci4gV2UgYWxzbyBjcmVhdGVkIHNlY3RvciBkdW1taWVzIGluIHRoaXMgc3RlcC4gV2UgdGhlbiBkcm9wcGVkIHNvbWUgdmFyaWFibGVzIHRoYXQgYXJlIG5vdCBpbiBvdXIgaW50ZXJlc3RzIGFuZCB0aGUgc3BsaXRlZCB0aGUgdGVzdGluZyBhbmQgdHJhaW5uaW5nIGRhdGEuIApgYGB7cn0KZmluYWxfZGF0YSA8LSBmaW5hbERBVEFTRVQgJT4lIAogICMgbWFrZSBTZWN0b3IgZHVtbXkgdmFyaWFibGVzCiAgZHVtbXlfY29scyhzZWxlY3RfY29sdW1ucyA9ICJTZWN0b3IiKSAlPiUgCiAgIyBtZXJnZSB3aXRoIHVwZGF0ZWQgbWFjcm8gZGF0YQogIHNlbGVjdCgtQ1BBTFRUMDFVU002NTdOX1BDMSwKICAgICAgICAgLUdEUCwKICAgICAgICAgLUdEUF9QQzEsCiAgICAgICAgIC1NMVNMX1BDMSAsCiAgICAgICAgIC1NMVNMLAogICAgICAgICAtUFJJQ0UsCiAgICAgICAgIC1TZWxsLAogICAgICAgICAtQ09NUEFOWSkgJT4lIAogIG1lcmdlKG1hY3JvX2RhdGEpICU+JSAKICAjIGNvbnZlcnQgTWFjcm8gZmFjdG9ycyBmcm9tIGNoYXJhY3RlcnMgdG8gbnVtZXJpYwogIG11dGF0ZShhY3Jvc3MoYygiQ1BBTFRUMDFVU002NTdOX1BDMSIsIkdEUCIsIkdEUF9QQzEiLCJUMTBZMlkiLCJNMVNMX1BDMSIsIk0xU0wiKSwKICAgICAgICAgICAgICAgIGFzLm51bWVyaWMpKSAlPiUgCiAgZ3JvdXBfYnkoWUVBUixTZWN0b3IpICU+JSAKICAjIHJlcGxhY2luZyB0aGUgbWlzc2luZyB2YWx1ZSB3aXRoIG1lZGlhbiBvZCB0aGUgaW5kdXN0cnkgaW4gdGhhdCB5ZWFyCiAgbXV0YXRlKGFjcm9zcyhjKERFQlRTLElOVkVTVE1FTlRTLENBU0gsVk9MVU1FLEVBUk5JTkdTLENPR1MsU0FMRVMsUkVDRUlWQUJMRSxJTlZFTlRPUlkpLH5yZXBsYWNlKC4sLj09MCxtZWRpYW4oLikpKSkgJT4lCiAgIyBkZWxldGUgbGVzcyBpbXBvcnRhbnQgZmFjdG9ycyAtPiBFeGNoYW5nZSBjYW4gcG9zc2liaWx5IGJlIGRlbGV0ZWQKICB1bmdyb3VwKCkgJT4lICAKICBtdXRhdGUoYWNyb3NzKGMoIXdoZXJlKGlzLm51bWVyaWMpLC0iTmFtZSIpLGFzLmZhY3RvcikpICU+JSAKICBzZWxlY3QoLUVhcm5pbmdzX25leHRfeWVhciwtb2JzZXJ2YXRpb25fZGF0ZSkgJT4lIAogIGRyb3BfbmEoKSAKCiMgZmlsdGVyIG91dCB0aGUgZGF0YSBmb3IgMjAyMQpmaW5hbF9kYXRhXzIwMjEgPC0gZmluYWxfZGF0YSAlPiUgCiAgZmlsdGVyKFlFQVIgPT0gMjAyMSkKCmZpbmFsX2RhdGEgPC0gZmluYWxfZGF0YSAlPiUgCiAgZmlsdGVyKFlFQVIgPCAyMDIxKQoKIyBzcGxpdCB0aGUgZGF0YQpzZXQuc2VlZCgzMjcpICNmb3IgcmVwcm9kdWNpYmlsaXR5CgpkYXRhX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoZmluYWxfZGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvcCA9IC43NSkKZGF0YV90cmFpbmluZyA8LSB0cmFpbmluZyhkYXRhX3NwbGl0KQpkYXRhX3Rlc3RpbmcgPC0gdGVzdGluZyhkYXRhX3NwbGl0KQoKIyBxdWljayBsb29rIG9mIHRoZSBkYXRhIApmaW5hbF9kYXRhICU+JSAKICBoZWFkKDUpCmBgYAoKIyMgRGF0YSBWaXN1YWxpemF0aW9uCldlIGZpcnN0IGV4cGxvcmVkIGRpc3RyaWJ1dGlvbnMgb2Ygb3VyIHByZWRpY3RvcnMgYW5kIG91dGNvbWUuIEFzIHRoZSBmb2xsb3dpbmcgZ3JhcGggc3VnZ2VzdGVkLCBhbGwgdGhlIG51bWVyaWMgdmFyaWFibGVzIGFyZSBzZXZlcmVseSByaWdodC1za2V3ZWQgd2l0aCBzb21lIG91dGxpZXJzLiBJbiBvdXIgZGF0YSwgdGhvdWdoIHRoZSBudW1iZXJzIG9mIGZpcm1zIGluIGVhY2ggc2VjdG9yIGFyZSB1bmJhbGFuY2VkLCB3ZSBnb3QgYSBkZWNlbnQgYW1vdW50IG9mIGRhdGEgZm9yIGVhY2ggc2VjdG9yLiAgCmBgYHtyfQoKICBmaW5hbF9kYXRhICU+JSAKICBzZWxlY3Qod2hlcmUoaXMubnVtZXJpYykpICU+JSAKICBzZWxlY3QoLXN0YXJ0c193aXRoKCJTZWN0b3JfIikpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9IGV2ZXJ5dGhpbmcoKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAidmFyaWFibGUiLCAKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIikgJT4lIAogIGdncGxvdChhZXMoeCA9IHZhbHVlKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAzMCkgKwogIGZhY2V0X3dyYXAodmFycyh2YXJpYWJsZSksIAogICAgICAgICAgICAgc2NhbGVzID0gImZyZWUiLAogICAgICAgICAgICAgbnJvdyA9IDQsCiAgICAgICAgICAgICBuY29sPTUpCgpgYGAKYGBge3J9CmZpbmFsX2RhdGEgJT4lIAogIHNlbGVjdCh3aGVyZShpcy5mYWN0b3IpKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBldmVyeXRoaW5nKCksCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gInZhcmlhYmxlIiwgCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ2YWx1ZSIpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSB2YWx1ZSkpICsKICBnZW9tX2JhcigpICsKICBmYWNldF93cmFwKHZhcnModmFyaWFibGUpLCAKICAgICAgICAgICAgIHNjYWxlcyA9ICJmcmVlIiwgCiAgICAgICAgICAgICBucm93ID0gNCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1KSkKCmBgYAoKICAKVGhlbiwgd2UgY29udGludWVkIHRvIGV4cGxvcmUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHJlZ3Jlc3NvcnMgYW5kIHRoZSBvdXRjb21lLiBCeSBvYnNlcnZhdGlvbiwgd2UgY2FuJ3Qgc2VlIGEgc3Ryb25nIGNvcnJlbGF0aW9uIGJldHdlZW4gZnVuZGVtZW50YWwgZmFjdG9ycywgc3VjaCBhcyBpbnZlc3RtZW50cyBhbmQgZGVidHMsIGFuZCBzdG9jayByZXR1cm4uIEl0IG1heSBpbmRpY2F0ZSB0aGF0IGxpbmVhciBtb2RlbCBpcyBub3QgYW4gaWRlYWwgbW9kZWwgaW4gdGhpcyBjYXNlLiAKCmBgYHtyfQpmaW5hbF9kYXRhICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBJTlZFU1RNRU5UUywgeSA9IFBST0ZJVCxjb2xvciA9IFlFQVIpKSArIAogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsgCiAgbGFicyh0aXRsZSA9ICJSZWxhdGlvbnNoaXAgYmV0d2VlbiBpbnZlc3RtZW50cyBhbmQgc3RvY2sgcmV0dXJuIikKYGBgCgpgYGB7cn0KZmluYWxfZGF0YSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gREVCVFMsIHkgPSBQUk9GSVQsY29sb3IgPSBZRUFSKSkgKyAKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSsKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArIAogIGxhYnModGl0bGUgPSAiUmVsYXRpb25zaGlwIGJldHdlZW4gZGVidHMgYW5kIHN0b2NrIHJldHVybiIpCmBgYAoKV2l0aCB0aGlzIGFuYW1pYXRpb24sIHdlIGNvdWxkIHNlZSB0aGF0IHRoZSByZXR1cm5zIG9mIHN0b2NrcyBmb2xsb3cgYSBjeWNsZSwgd2hpY2ggbWF5IGJlIGluZmx1ZW5jZWQgYnkgbWFjcm9lY29ub21pYyBjb25kaXRpb24uIFRoZSByZXR1cm4gaW4gMTk5OSBhbmQgMjAxOSBzZWVtcyB0byBiZSB0aGUgaGlnaGVzdCBmb3IgYWxsIGluZHVzdHJpZXMuIEFsc28sIHdlIG5vdGljZWQgdGhhdCBzb21lIGluZHVzdHJpZXMgdmFyeSBhIGxvdCB5ZWFyIHRvIHllYXIsIHN1Y2ggYXMgSVQgYW5kIGhlYWx0aGNhcmUgaW5kdXN0cnkuIAoKYGBge3IgZXZhbCA9IEZBTFNFfQpzZWN0b3JfcmV0dXJuX2FuPC1maW5hbF9kYXRhICU+JQogIGdncGxvdChhZXMoeCA9IFBST0ZJVCwgeSA9IFNlY3RvcikpICsKICBnZW9tX2JveHBsb3QoYWVzKGNvbG9yID0gU2VjdG9yKSwKICAgICAgICAgICAgIGFscGhhID0gLjgsCiAgICAgICAgICAgICBzaXplID0gMSkgKyAKICBsYWJzKHRpdGxlID0gIlNwcmVhZCBvZiBzdG9jayByZXR1cm4gaW4gZGlmZmVyZW50IHNlY3RvciIsCiAgICAgICBzdWJ0aXRsZSA9ICJZRUFSOiB7Y2xvc2VzdF9zdGF0ZX0iLAogICAgICAgY29sb3IgPSAiIikgKyAKICB0cmFuc2l0aW9uX3N0YXRlcyhZRUFSKQogIAphbmltYXRlKHNlY3Rvcl9yZXR1cm5fYW4sIGR1cmF0aW9uID0gMjUpCmFuaW1fc2F2ZSgic2VjdG9yX3JldHVybi5naWYiKQpgYGAKCmBgYHtyfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygic2VjdG9yX3JldHVybi5naWYiKQpgYGAKCldlIGZ1cnRoZXIgZXhwbG9yZWQgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBpbmR1c3RyeSBhbmQgdGhlIHBvdGVudGlhbCBtYWNyb2Vjb25vbWljIGluZmx1ZW5jZXIgdXNpbmcgZ3JhcGhzIGJlbG93LiBUaG91Z2ggdGhlIHJlbGF0aW9uc2hpcCBkb2Vzbid0IHNlZW0gdG8gYmUgbGluZWFyLCB3ZSBkbyBzZWUgaG93IG1hY3JvZWNvbm9taWMgY29uZGl0aW9uIGFmZmVjdCBlYWNoIGluZHVzdHJ5IGRpZmZlcmVudGx5IGFuZCB3aWxsIGFjY291bnQgZm9yIHRoYXQgaW4gb3VyIG1vZGVsLgoKYGBge3J9CmZpbmFsX2RhdGEgJT4lIAogIGdyb3VwX2J5KFlFQVIsU2VjdG9yKSAlPiUgCiAgbXV0YXRlKHByb2ZpdF9wb3IgPSBQUk9GSVQqYE1BUktFVCBDQVBgLyhzdW0oYE1BUktFVCBDQVBgLG5hLnJtID0gVFJVRSkpKSAlPiUgCiAgc3VtbWFyaXNlKFNlY3Rvcl9wcm9maXQgPSBzdW0ocHJvZml0X3BvciksCiAgICAgICAgICAgIEdEUF9QQzEgPSBtZWFuKEdEUF9QQzEpLAogICAgICAgICAgICBDUEFMVFQwMVVTTTY1N05fUEMxID0gbWVhbihDUEFMVFQwMVVTTTY1N05fUEMxKSwKICAgICAgICAgICAgU2VjdG9yID0gU2VjdG9yWzFdKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gQ1BBTFRUMDFVU002NTdOX1BDMSx5ID0gU2VjdG9yX3Byb2ZpdCxjb2xvciA9IFNlY3RvcikpKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsgCiAgbGFicyh0aXRsZSA9ICJSZWxhdGlvbnNoaXAgYmV0d2VlbiBpbmZsYXRpb24gYW5kIGF2ZXJhZ2UgcmV0dXJuIG9mIGEgaW5kdXN0cnkiLAogICAgICAgeCA9ICJQZXJjZW50IENoYW5nZSBpbiBDUEkiLAogICAgICAgeSA9ICJBdmVyYWdlIFJldHVybiBvZiBhIHNlY3RvciIpCiAgCmBgYAoKYGBge3J9CmZpbmFsX2RhdGEgJT4lIAogIGdyb3VwX2J5KFlFQVIsU2VjdG9yKSAlPiUgCiAgbXV0YXRlKHByb2ZpdF9wb3IgPSBQUk9GSVQqYE1BUktFVCBDQVBgLyhzdW0oYE1BUktFVCBDQVBgLG5hLnJtID0gVFJVRSkpKSAlPiUgCiAgc3VtbWFyaXNlKFNlY3Rvcl9wcm9maXQgPSBzdW0ocHJvZml0X3BvciksCiAgICAgICAgICAgIEdEUF9QQzEgPSBtZWFuKEdEUF9QQzEpLAogICAgICAgICAgICBDUEFMVFQwMVVTTTY1N05fUEMxID0gbWVhbihDUEFMVFQwMVVTTTY1N05fUEMxKSwKICAgICAgICAgICAgU2VjdG9yID0gU2VjdG9yWzFdKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gR0RQX1BDMSx5ID0gU2VjdG9yX3Byb2ZpdCxjb2xvciA9IFNlY3RvcikpKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsgCiAgbGFicyh0aXRsZSA9ICJSZWxhdGlvbnNoaXAgYmV0d2VlbiBHRFAgIGFuZCBhdmVyYWdlIHJldHVybiBvZiBhIGluZHVzdHJ5IiwKICAgICAgIHggPSAiUGVyY2VudCBDaGFuZ2UgaW4gR0RQIiwKICAgICAgIHkgPSAiQXZlcmFnZSBSZXR1cm4gb2YgYSBzZWN0b3IiKQpgYGAKCgojIyBMYXNzbyBNb2RlbCAKIyMjIEJ1aWxkaW5nIFJlY2lwZQoKSW4gdGhlIGxhc3NvIG1vZGVsLCB0byBhY2NvdW50IGZvciB0aGUgZmFjdCB0aGUgbWFjcm9lY29ub21pYyBjb25kaXRpb24gYWZmZWN0cyBlYWNoIGluZHVzdHJ5IGRpZmZlcmVudGx5LCB3ZSBhbHNvIGluY2x1ZGVkIHRoZSBpbnRlcmFjdGlvbiB0ZXJtIGJldHdlZW4gR0RQIGFuZCBzZWN0b3IgZHVtbWllcy4gCgpgYGB7cn0KcmV0dXJuX3JlY2lwZSA8LSByZWNpcGUoUFJPRklUIH4gLiwgI3Nob3J0LWN1dCwgLiA9IGFsbCBvdGhlciB2YXJzCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGFfdHJhaW5pbmcpICU+JSAKICAjIGZpbHRlciB0byBvbmx5IGhhdmUgZGF0YSBhZnRlciAyMDIwCiAgc3RlcF9maWx0ZXIoWUVBUjwyMDIxKSAlPiUgCiAgc3RlcF9ybShOYW1lLFNlY3RvcixZRUFSKSAlPiUgCiAgI3N0ZXBfcm0oR0RQLE0xU0wsTmFtZSxTZWN0b3IsWUVBUikgJT4lIAogICMgYWRkIFBFIAogIHN0ZXBfbXV0YXRlKFBFID0gYE1BUktFVCBDQVBgL0VBUk5JTkdTKSAlPiUKICAjIE5vcm1hbGl6ZSBhbGwgdmFyaWFibGVzIGV4Y2VwdCBmb3IgR0RQCiAgc3RlcF9ub3JtYWxpemUoYWxsX3ByZWRpY3RvcnMoKSwgCiAgICAgICAgICAgICAgICAgLWFsbF9ub21pbmFsKCksCiAgICAgICAgICAgICAgICAgLXN0YXJ0c193aXRoKCJTZWN0b3JfIikpICU+JSAKICAjIENyZWF0ZSBpbnRlcmFjdGlvbiB0ZXJtcwogIHN0ZXBfaW50ZXJhY3QodGVybXMgPSB+YyhHRFBfUEMxKTpzdGFydHNfd2l0aCgiU2VjdG9yXyIpKSAKIyBzaG93IHRoZSBkYXRhIGluIHJlY2lwZQpyZXR1cm5fcmVjaXBlICU+JSAKICBwcmVwKGRhdGFfdHJhaW5pbmcpICU+JQogICMgdXNpbmcgYmFrZShuZXdfZGF0YSA9IE5VTEwpIGdpdmVzIHNhbWUgcmVzdWx0IGFzIGp1aWNlKCkKICAjIGJha2UobmV3X2RhdGEgPSBOVUxMKQogIGp1aWNlKCkgCmBgYAoKIyMjIFNlbGVjdCB0dW5pbmcgcGFyYW1ldGVyCgpgYGB7cn0KcmV0dXJuX2xpbmVhcl9tb2QgPC0gCiAgIyBEZWZpbmUgYSBsYXNzbyBtb2RlbCAKICAjIEkgYmVsaWV2ZSBkZWZhdWx0IGlzIG1peHR1cmUgPSAxIHNvIHByb2JhYmx5IGRvbid0IG5lZWQgCiAgbGluZWFyX3JlZyhtaXh0dXJlID0gMSkgJT4lIAogICMgU2V0IHRoZSBlbmdpbmUgdG8gImdsbW5ldCIgCiAgc2V0X2VuZ2luZSgiZ2xtbmV0IikgJT4lIAogICMgVGhlIHBhcmFtZXRlcnMgd2Ugd2lsbCB0dW5lLgogIHNldF9hcmdzKHBlbmFsdHkgPSB0dW5lKCkpICU+JSAKICAjIFVzZSAicmVncmVzc2lvbiIKICBzZXRfbW9kZSgicmVncmVzc2lvbiIpCgpzZXQuc2VlZCg0NTYpCnJldHVybl9sbV93ZiA8LSAKICAjIFNldCB1cCB0aGUgd29ya2Zsb3cKICB3b3JrZmxvdygpICU+JSAKICAjIEFkZCB0aGUgcmVjaXBlCiAgYWRkX3JlY2lwZShyZXR1cm5fcmVjaXBlKSAlPiUgCiAgIyBBZGQgdGhlIG1vZGVsaW5nCiAgYWRkX21vZGVsKHJldHVybl9saW5lYXJfbW9kKQoKcGVuYWx0eV9ncmlkIDwtIGdyaWRfcmVndWxhcihwZW5hbHR5KCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gMTApCgpyZXR1cm5fY3YgPC0gdmZvbGRfY3YoZGF0YV90cmFpbmluZywgdiA9IDUpCgpyZXR1cm5fbG1fdHVuZSA8LSAKICByZXR1cm5fbG1fd2YgJT4lIAogIHR1bmVfZ3JpZCgKICAgIHJlc2FtcGxlcyA9IHJldHVybl9jdiwKICAgIGdyaWQgPSBwZW5hbHR5X2dyaWQKICAgICkKCmJlc3RfcGFyYW08LXJldHVybl9sbV90dW5lICU+JSAKICBzZWxlY3RfYmVzdChtZXRyaWMgPSAicm1zZSIpCgpyZXR1cm5fbGFzc29fZmluYWxfd2YgPC0gcmV0dXJuX2xtX3dmICU+JSAKICBmaW5hbGl6ZV93b3JrZmxvdyhiZXN0X3BhcmFtKQoKcmV0dXJuX2xhc3NvX2ZpbmFsX21vZCA8LSByZXR1cm5fbGFzc29fZmluYWxfd2YgJT4lIAogIGZpdChkYXRhID0gZGF0YV90cmFpbmluZykKCiMgdmlzdWxpemF0aW9uIGZvciBiZXN0IHBhcmFtCnNldC5zZWVkKDQ1NikKcmV0dXJuX2xtX3R1bmUgJT4lIAogIGNvbGxlY3RfbWV0cmljcygpICU+JSAKICBmaWx0ZXIoLm1ldHJpYyA9PSAicm1zZSIpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBwZW5hbHR5LCB5ID0gbWVhbikpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsKICBzY2FsZV94X2xvZzEwKAogICBicmVha3MgPSBzY2FsZXM6OnRyYW5zX2JyZWFrcygibG9nMTAiLCBmdW5jdGlvbih4KSAxMF54KSwKICAgbGFiZWxzID0gc2NhbGVzOjp0cmFuc19mb3JtYXQoImxvZzEwIixzY2FsZXM6Om1hdGhfZm9ybWF0KDEwXi54KSkpICsKICBsYWJzKHggPSAicGVuYWx0eSIsIHkgPSAicm1zZSIpCmBgYAoKCgojIyMgTGFzc28gcmVzdWx0cwoKVGhlIHRhYmxlIGJlbG93IHNob3dzIHRoZSBlc3RpbWF0ZSBvZiBlYWNoIHByZWRpY3RvciBpbiB0aGUgTGFzc28gTW9kZWwuICBXZSBjYW4gc2VlIHRoYXQgaW5kaWNhdG9ycyBpbiBtYWNyb2Vjb25vbWljcyBhcmUgcmVsYXRpdmVseSBpbXBvcnRhbnQgdG8gcHJlZGljdCB0aGUgc3RvY2sgcmV0dXJuLiBUaGUgY2hhbmdlIGluIGluZmxhdGlvbiBhbmQgR0RQIGFsbCByZW1haW4gc2lnbmlmaWNhbnQgYWZ0ZXIgc2hyaW5raW5nLiBUaGUgaW50ZXJhY3Rpb24gdGVybXMgYmV0d2VlbiBHRFAgYW5kIGluZHVzdHJpZXMgYWxzbyBzaG93ZWQgaW1wb3J0YW5jZSwgYWNjb3VudGluZyBmb3IgdGhlIGZhY3QgdGhhdCB0aGUgbWFjcm9lY29ub21pYyBjb25kaXRpb24gYWZmZWN0cyBlYWNoIGluZHVzdHJ5IGRpZmZlcmVudGx5OiBHRFAgc2VlbXMgdG8gYWZmZWN0IHRoZSBzdG9jayByZXR1cm4gaW4gQ29tbXVuaWNhdGlvbiBTZXJ2aWNlcywgRW5lcmd5LCBhbmQgQ29uc3VtZXIgRGlzY3JldGlvbmFyeSBzZWN0b3JzIGxlc3MuIAoKYGBge3J9CnJldHVybl9sYXNzb19maW5hbF9tb2QgJT4lIAogIHB1bGxfd29ya2Zsb3dfZml0KCkgJT4lIAogIHRpZHkoKSAlPiUgCiAgYXJyYW5nZShkZXNjKGVzdGltYXRlKSkgJT4lIAogIGtibCgpICU+JSAKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJib3JkZXJlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkgJT4lIAogIHNjcm9sbF9ib3god2lkdGggPSAiMTAwJSIsIGhlaWdodCA9ICI1MDBweCIpCmBgYAoKIyMjIE1vZGVsIEV2YWx1YXRpb24KCiMjIyMgUHJlZGljaXRvbiBwcmVjaXNpb24gCgpUaGUgTGFzc28gbW9kZWwgZG9lcyBub3QgcGVyZm9ybSB3ZWxsLiBJdCBvbmx5IGV4cGxhaW5zIDIwJSBvZiB0aGUgdmFyYWluY2UgaW4gc3RvY2sgcmV0dXJuIGFuZCBpdCBhbHNvIGhhcyBhIHJlbGF0aXZlbHkgaGlnaCBybXNlIG9mIGFyb3VuZCA0Ni4gCgpgYGB7cn0KcmV0dXJuX2xtX3R1bmUgJT4lCiAgc2VsZWN0KGlkLCAubWV0cmljcykgJT4lCiAgdW5uZXN0KC5tZXRyaWNzKSAlPiUKICBmaWx0ZXIoLm1ldHJpYyA9PSAicnNxIikgJT4lIAogIGZpbHRlciguY29uZmlnID09ICJQcmVwcm9jZXNzb3IxX01vZGVsMDgiKSAlPiUgCiAgc3VtbWFyaXNlKG1lYW5fcnNxID0gbWVhbiguZXN0aW1hdGUpKQoKcmV0dXJuX2xtX3R1bmUgJT4lCiAgc2VsZWN0KGlkLCAubWV0cmljcykgJT4lCiAgdW5uZXN0KC5tZXRyaWNzKSAlPiUKICBmaWx0ZXIoLm1ldHJpYyA9PSAicm1zZSIpICU+JSAKICBmaWx0ZXIoLmNvbmZpZyA9PSAiUHJlcHJvY2Vzc29yMV9Nb2RlbDA4IikgJT4lIAogIHN1bW1hcmlzZShtZWFuX3Jtc2UgPSBtZWFuKC5lc3RpbWF0ZSkpCmBgYAoKYGBge3IgZXZhbCA9IEZBTFNFfQpzZXQuc2VlZCg0NTYpCnByZWRpY3Rpb24gPC0gcHJlZGljdCgKICByZXR1cm5fbGFzc29fZmluYWxfbW9kLAogIG5ld19kYXRhID0gZGF0YV90cmFpbmluZykKCnRyYWluaW5nX3ByZWQ8LWRhdGFfdHJhaW5pbmcgJT4lIAogIG11dGF0ZSgucHJlZCA9IHByZWRpY3Rpb24kLnByZWQpCiAgCiAgCnRyYWluaW5nX3ByZWQgJT4lIAogIGdncGxvdChhZXMoeCA9IFBST0ZJVCwgCiAgICAgICAgICAgICB5ID0gLnByZWQsCiAgICAgICAgICAgICBjb2xvciA9IFlFQVIpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IC41LCAKICAgICAgICAgICAgIHNpemUgPSAuNSkgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsKICBnZW9tX2FibGluZShzbG9wZSA9IDEsIAogICAgICAgICAgICAgIGludGVyY2VwdCA9IDAsIAogICAgICAgICAgICAgIGNvbG9yID0gImRhcmtyZWQiKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IE5hbWUpLGxhYmVsLnNpemUgPSAwLjE1LGRhdGEgPSB0cmFpbmluZ19wcmVkICU+JSBmaWx0ZXIoUFJPRklUPjQwMCkgKSsKICBsYWJzKHggPSAiQWN0dWFsIFJldHVybiIsIAogICAgICAgeSA9ICJQcmVkaWN0ZWQgUmV0dXJuIikgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYigpCmBgYAoKIyMjIyBPdmVyZml0dGluZwoKQmVsb3cgc2hvd3MgdGhlIHItc3F1YXJlIGFuZCBybXNlIG9uIHRlc3RpbmcgZGF0YS4gVGhleSBhcmUgdmVyeSBzaW1pbGFyIHRvIHRob3NlIG9uIHRyYWlubmluZyBkYXRhIHNvIHdlIHdvdWxkIHNhZmVseSBjb25jbHVkZSB0aGF0IG91ciBtb2RlbCBkaWQgbm90IG92ZXJmaXQuIApgYGB7cn0KcmV0dXJuX2xhc3NvX3Rlc3QgPC0gcmV0dXJuX2xhc3NvX2ZpbmFsX3dmICU+JSAKICBsYXN0X2ZpdChkYXRhX3NwbGl0KQoKIyBNZXRyaWNzIGZvciBtb2RlbCBhcHBsaWVkIHRvIHRlc3QgZGF0YQpyZXR1cm5fbGFzc29fdGVzdCAlPiUgCiAgY29sbGVjdF9tZXRyaWNzKCkKYGBgCgpUaGUgZm9sbG93aW5nIGdyYXBocyBzaG93IHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgbW9kZWwgb24gdGhlIHRlc3RpbmcgZGF0YTogCgpgYGB7cn0KdGVzdF9wcmVkaWN0aW9uIDwtIHByZWRpY3QoCiAgcmV0dXJuX2xhc3NvX2ZpbmFsX21vZCwKICBuZXdfZGF0YSA9IGRhdGFfdGVzdGluZykKCnRlc3RpbmdfbGFzc29fcHJlZDwtZGF0YV90ZXN0aW5nICU+JSAKICBtdXRhdGUoLnByZWQgPSB0ZXN0X3ByZWRpY3Rpb24kLnByZWQpCiAgCiAgCnRlc3RpbmdfbGFzc29fcHJlZCAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gUFJPRklULCAKICAgICAgICAgICAgIHkgPSAucHJlZCwKICAgICAgICAgICAgIGNvbG9yID0gWUVBUikpICsKICBnZW9tX3BvaW50KGFscGhhID0gLjUsIAogICAgICAgICAgICAgc2l6ZSA9IC41KSArCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSkgKwogIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgCiAgICAgICAgICAgICAgaW50ZXJjZXB0ID0gMCwgCiAgICAgICAgICAgICAgY29sb3IgPSAiZGFya3JlZCIpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gTmFtZSksbGFiZWwuc2l6ZSA9IDAuMTUsZGF0YSA9IHRlc3RpbmdfbGFzc29fcHJlZCAlPiUgZmlsdGVyKFBST0ZJVD4zMDApICkrCiAgbGFicyh4ID0gIkFjdHVhbCBSZXR1cm4iLCAKICAgICAgIHkgPSAiUHJlZGljdGVkIFJldHVybiIpICsgCiAgc2NhbGVfY29sb3JfdmlyaWRpc19iKCkKYGBgCgojIyMgSW50ZXJwcmV0YWJsZSBNYWNoaW5lIExlYXJuaW5nIAoKQWdhaW4sIHRoZSBib3hwbG90IGFuZCB0aGUgaGlzdG9ncmFtIG9mIHJlc2lkdWFscywgdGhlIG1vZGVsIGRvZXMgbm90IHByZWRpY3QgdGhlIHJldHVybiB3ZWxsIGFuZCB0aGlzIG1heSBiZWNhdXNlIHNvbWUgZXh0cmVtZSB2YWx1ZXMuIApgYGB7cn0KbGFzc29fZXhwbGFpbiA8LSAKICBleHBsYWluX3RpZHltb2RlbHMoCiAgICBtb2RlbCA9IHJldHVybl9sYXNzb19maW5hbF9tb2QsCiAgICBkYXRhID0gZGF0YV90cmFpbmluZyAlPiUgc2VsZWN0KC1QUk9GSVQpLCAKICAgIHkgPSBkYXRhX3RyYWluaW5nICU+JSAgcHVsbChQUk9GSVQpLAogICAgbGFiZWwgPSAibGFzc28iCiAgKQpgYGAKCmBgYHtyfQpsYXNzb19tb2RfcGVyZiA8LSBtb2RlbF9wZXJmb3JtYW5jZShsYXNzb19leHBsYWluKQpoaXN0X3Bsb3QgPC0gCiAgcGxvdChsYXNzb19tb2RfcGVyZiwKICAgICAgIGdlb20gPSAiaGlzdG9ncmFtIikKYm94X3Bsb3QgPC0KICBwbG90KGxhc3NvX21vZF9wZXJmLAogICAgICAgZ2VvbSA9ICJib3hwbG90IikKCmhpc3RfcGxvdApib3hfcGxvdAoKYGBgCgpCZWxvdyBzaG93cyB0aGUgZmVhdHVyZSBpbXBvcnRhbmNlIHBsb3RzIGdlbmVyYXRlZCBieSB0d28gZGlmZmVyZW50IG1ldGhvZHMuIFdlIGNvdWxkIHNlZSB0aGF0IG1hY3JvZWNvbm9taWMgaW5kaWNhdG9ycyByZW1haW5lZCBpbXBvcnRhbmNlIGluIGJvdGggb2YgdGhlIHBsb3RzIGJ1dCBtYXJrZXQgY2FwIHNlZW1zIHRvIGJlIGFuIGNydWNpYWwgZmFjdG9yIHdoZW4gd2UgdXNlIHBlcm11dGF0aW9uIG1ldGhvZC4KCmBgYHtyfQpzZXQuc2VlZCgxMCkgI3NpbmNlIHdlIGFyZSBzYW1wbGluZyAmIHBlcm11dGluZywgd2Ugc2V0IGEgc2VlZCBzbyB3ZSBjYW4gcmVwbGljYXRlIHRoZSByZXN1bHRzCmxhc3NvX3Zhcl9pbXAgPC0gCiAgbW9kZWxfcGFydHMoCiAgICBsYXNzb19leHBsYWluCiAgICApCgpwbG90KGxhc3NvX3Zhcl9pbXAsIHNob3dfYm94cGxvdHMgPSBUUlVFKQpgYGAKYGBge3J9CnJldHVybl9sYXNzb19maW5hbF9tb2QgJT4lIAogIHB1bGxfd29ya2Zsb3dfZml0KCkgJT4lIAogIHZpcCgpCmBgYAoKIyMgUmFuZG9tIEZvcmVzdCBNb2RlbCAKCiMjIyBCdWlsZGluZyBSYW5kb20gRm9yZXN0IG1vZGVsIAoKVG8gYnVpbGQgdGhlIHJhbmRvbSBmb3Jlc3QgbW9kZWwsIHdlIHNldCB1cCByZWNpcGUgdXNpbmcgb3VyIHRyYWluaW5nIGRhdGEgKGBkYXRhX3RyYWluaW5nYCksIGRlZmluZSBtb2RlbCB3aXRoIGBtdHJ5ID0gNmAsIGBtaW5fbiA9IDEwYCBhbmQgYG51bWJlcnMgb2YgdHJlZSA9IDIwMGAsIGNyZWF0ZSByYW5nZXIgd29ya2Zsb3csIGFuZCB0aGVuIHdlIGNhbiBmaXQgdGhlIG1vZGVsLiAKCmBgYHtyfQojIHNldCB1cCByZWNpcGUgCnJhbmdlcl9yZWNpcGUgPC0KICByZWNpcGUoUFJPRklUIH4gLiwgI3Nob3J0LWN1dCwgLiA9IGFsbCBvdGhlciB2YXJzCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGFfdHJhaW5pbmcpICU+JQogIHN0ZXBfZmlsdGVyKFlFQVI8MjAyMSkgJT4lIAogICMgcmVtb3ZlIHRoZSB1bndhbnRlZCB2YXJpYWJsZXMKICBzdGVwX3JtKFlFQVIsTmFtZSxHRFAsTTFTTCxTZWN0b3IpICU+JSAKICAjIGFkZCBQRSAKICBzdGVwX211dGF0ZShQRSA9IGBNQVJLRVQgQ0FQYC9FQVJOSU5HUykKCiNkZWZpbmUgbW9kZWwKcmFuZ2VyX3NwZWMgPC0gCiAgcmFuZF9mb3Jlc3QobXRyeSA9IDYsIAogICAgICAgICAgICAgIG1pbl9uID0gMTAsIAogICAgICAgICAgICAgIHRyZWVzID0gMjAwKSAlPiUgCiAgc2V0X21vZGUoInJlZ3Jlc3Npb24iKSAlPiUgCiAgc2V0X2VuZ2luZSgicmFuZ2VyIikKCiNjcmVhdGUgd29ya2Zsb3cKcmFuZ2VyX3dvcmtmbG93IDwtIAogIHdvcmtmbG93KCkgJT4lIAogIGFkZF9yZWNpcGUocmFuZ2VyX3JlY2lwZSkgJT4lIAogIGFkZF9tb2RlbChyYW5nZXJfc3BlYykKCiNmaXQgdGhlIG1vZGVsCnNldC5zZWVkKDcxMikgIyBmb3IgcmVwcm9kdWNpYmlsaXR5IC0gcmFuZG9tIHNhbXBsaW5nIGluIHJhbmRvbSBmb3Jlc3QgY2hvb3NpbmcgbnVtYmVyIG9mIHZhcmlhYmxlcwpyYW5nZXJfZml0IDwtIHJhbmdlcl93b3JrZmxvdyAlPiUgCiAgZml0KGRhdGFfdHJhaW5pbmcpCmBgYAoKIyMjIFJhbmRvbSBGb3Jlc3QgcmVzdWx0cwoKVGhlIHRhYmxlIGJlbG93IHNob3dzIHRoZSBPT0IgZXJyb3IgKE1TRSksIE9PQiBSTVNFIChSb290IG1lYW4gc3F1YXJlIGVycm9yKSwgYW5kIFIgc3F1YXJlZCBvZiB0aGUgc3RvY2sgcmV0dXJuIHByZWRpY3Rpb25zIHVzaW5nIHRoZSByYW5kb20gZm9yZXN0IG1vZGVsIGFib3ZlLgoKT09CIGVycm9yOiAKYGBge3J9CnJhbmdlcl9maXQkZml0JGZpdCRmaXQkcHJlZGljdGlvbi5lcnJvcgpgYGAKCk9PQiBSTVNFOiAKYGBge3J9CnNxcnQocmFuZ2VyX2ZpdCRmaXQkZml0JGZpdCRwcmVkaWN0aW9uLmVycm9yKQpgYGAKClIgU3F1YXJlZDogCmBgYHtyfQpyYW5nZXJfZml0JGZpdCRmaXQkZml0JHIuc3F1YXJlZApgYGAKCiMjIyBNb2RlbCBFdmFsdWF0aW9uCiMjIyMgUHJlZGljaXRvbiBwcmVjaXNpb24gKFRyYW5pbmcgZGF0YSkKCkV2ZW4gdGhvdWdoIHRoZSBtZWFuIHJtc2UgZnJvbSB0aGUgcmFuZG9tIGZvcmVzdCAoMzkuMzA3ODcpIHNlZW1zIHRvIGJlIGZhaXJseSBoaWdoIGJ1dCBpdCBpcyBzdGlsbCBsb3dlciB0aGFuIHRoZSBMQVNTTyBtb2RlbCBvZiA0Ni4gVGh1cywgcmFuZG9tIGZvcmVzdCBtb2RlbCBwZXJmb3JtcyBiZXR0ZXIuIAoKYGBge3J9CnNldC5zZWVkKDEyMTEpICMgZm9yIHJlcHJvZHVjaWJpbGl0eQpkYXRhX2N2IDwtIHZmb2xkX2N2KGRhdGFfdHJhaW5pbmcsIHYgPSA1KQoKbWV0cmljIDwtIG1ldHJpY19zZXQocm1zZSkKY3RybF9yZXMgPC0gY29udHJvbF9zdGFja19yZXNhbXBsZXMoKQoKcmFuZ2VyX2N2IDwtIHJhbmdlcl93b3JrZmxvdyAlPiUgCiAgZml0X3Jlc2FtcGxlcyhkYXRhX2N2LCAKICAgICAgICAgICAgICAgIG1ldHJpY3MgPSBtZXRyaWMsCiAgICAgICAgICAgICAgICBjb250cm9sID0gY3RybF9yZXMpCgojIEV2YWx1YXRpb24gbWV0cmljcyBhdmVyYWdlZCBvdmVyIGFsbCBmb2xkczoKY29sbGVjdF9tZXRyaWNzKHJhbmdlcl9jdikKYGBgCgpXZSBhbHNvIHBsb3QgYSBncmFwaCBzaG93aW5nIHRoZSBhY3R1YWwgcmV0dXJuIHZzIHByZWRpY3RlZCByZXR1cm4uIAoKYGBge3J9CnJhbmdlcl9wcmVkaWN0aW9uIDwtIHByZWRpY3QoCiAgcmFuZ2VyX2ZpdCwKICBuZXdfZGF0YSA9IGRhdGFfdHJhaW5pbmcpCgpyYW5nZXJfdHJhaW5pbmdfcHJlZDwtZGF0YV90cmFpbmluZyAlPiUgCiAgbXV0YXRlKC5wcmVkID0gcmFuZ2VyX3ByZWRpY3Rpb24kLnByZWQpCiAgCiAgCnJhbmdlcl90cmFpbmluZ19wcmVkICU+JQogIGdncGxvdChhZXMoeCA9IFBST0ZJVCwKICAgICAgICAgICAgIHkgPSAucHJlZCkpICsKICBnZW9tX3BvaW50KGFscGhhID0gLjUsCiAgICAgICAgICAgICBzaXplID0gLjUpICsKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLAogICAgICAgICAgICAgIGludGVyY2VwdCA9IDAsCiAgICAgICAgICAgICAgY29sb3IgPSAiZGFya3JlZCIpICsKICBsYWJzKHggPSAiQWN0dWFsIFJldHVybiIsCiAgICAgICB5ID0gIlByZWRpY3RlZCBSZXR1cm4iKQoKYGBgCgojIyMjIFByZWRpY2l0b24gcHJlY2lzaW9uIChUZXN0aW5nIGRhdGEpCgpUaGUgdGFibGUgYmVsb3cgc2hvd3MgdGhlIHJtc2Ugb24gdGVzdGluZyBkYXRhLiBJdCBpcyBhIGJpdCBoaWdoZXIgYnV0IGZhaXJseSBzaW1pbGFyIHRvIHRoZSBlcnJvciBvbiB0cmFpbmluZyBkYXRhIHNvIHdlIGNvdWxkIHNheSB0aGF0IG91ciBtb2RlbCBkaWQgbm90IG92ZXJmaXQuIAoKYGBge3J9CnNldC5zZWVkKDEyMTEpICMgZm9yIHJlcHJvZHVjaWJpbGl0eQpkYXRhX2N2IDwtIHZmb2xkX2N2KGRhdGFfdGVzdGluZywgdiA9IDUpCgptZXRyaWMgPC0gbWV0cmljX3NldChybXNlKQpjdHJsX3JlcyA8LSBjb250cm9sX3N0YWNrX3Jlc2FtcGxlcygpCgpyYW5nZXJfY3YgPC0gcmFuZ2VyX3dvcmtmbG93ICU+JSAKICBmaXRfcmVzYW1wbGVzKGRhdGFfY3YsIAogICAgICAgICAgICAgICAgbWV0cmljcyA9IG1ldHJpYywKICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBjdHJsX3JlcykKCiMgRXZhbHVhdGlvbiBtZXRyaWNzIGF2ZXJhZ2VkIG92ZXIgYWxsIGZvbGRzOgpjb2xsZWN0X21ldHJpY3MocmFuZ2VyX2N2KQpgYGAKCkJlbG93IGlzIHRoZSBncmFwaCBzaG93aW5nIHRoZSBhY3R1YWwgcmV0dXJuIHZzLiBwcmVkaWN0ZWQgcmV0dXJuIG9uIHRlc3RpbmcgZGF0YS4gCgpgYGB7cn0KcmFuZ2VyX3Rlc3RfcHJlZGljdGlvbiA8LSBwcmVkaWN0KAogIHJhbmdlcl9maXQsCiAgbmV3X2RhdGEgPSBkYXRhX3Rlc3RpbmcpCgpyYW5nZXJfdGVzdF9wcmVkPC1kYXRhX3Rlc3RpbmcgJT4lIAogIG11dGF0ZSgucHJlZCA9IHJhbmdlcl90ZXN0X3ByZWRpY3Rpb24kLnByZWQpCiAgCiAgCnJhbmdlcl90ZXN0X3ByZWQgJT4lCiAgZ2dwbG90KGFlcyh4ID0gUFJPRklULAogICAgICAgICAgICAgeSA9IC5wcmVkKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAuNSwKICAgICAgICAgICAgIHNpemUgPSAuNSkgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsKICBnZW9tX2FibGluZShzbG9wZSA9IDEsCiAgICAgICAgICAgICAgaW50ZXJjZXB0ID0gMCwKICAgICAgICAgICAgICBjb2xvciA9ICJkYXJrcmVkIikgKwogIGxhYnMoeCA9ICJBY3R1YWwgUmV0dXJuIiwKICAgICAgIHkgPSAiUHJlZGljdGVkIFJldHVybiIpCgpgYGAKCiMjIyBJbnRlcnByZXRhYmxlIE1hY2hpbmUgTGVhcm5pbmcgCgpCYXNlZCBvbiB0aGUgYm94LXBsb3QgYW5kIHRoZSBoaXN0b2dyYW0gYmVsb3csIHRoZSByZXNpZHVhbHMgbW9zdGx5IGxpZSBiZXR3ZWVuIC01MCB0byA1MC4gQnV0IHRoZXJlIGFyZSBzb21lIGEgZmV3IG91dGxpbmVycyB0aGF0IGNhbiBnbyB1cCB0byA0MDAuIAoKYGBge3J9CnJmX2V4cGxhaW4gPC0gCiAgZXhwbGFpbl90aWR5bW9kZWxzKAogICAgbW9kZWwgPSByYW5nZXJfZml0LAogICAgZGF0YSA9IGRhdGFfdHJhaW5pbmcgJT4lIHNlbGVjdCgtUFJPRklUKSwgCiAgICB5ID0gZGF0YV90cmFpbmluZyAlPiUgIHB1bGwoUFJPRklUKSwKICAgIGxhYmVsID0gInJmIgogICkKcmZfbW9kX3BlcmYgPC0gIG1vZGVsX3BlcmZvcm1hbmNlKHJmX2V4cGxhaW4pCgpoaXN0X3Bsb3QgPC0gCiAgcGxvdChyZl9tb2RfcGVyZiwgCiAgICAgICBnZW9tID0gImhpc3RvZ3JhbSIpCmJveF9wbG90IDwtCiAgcGxvdChyZl9tb2RfcGVyZiwgCiAgICAgICBnZW9tID0gImJveHBsb3QiKQoKaGlzdF9wbG90CmJveF9wbG90CmBgYAoKQWNjb3JkaW5nIHRvIHRoZSBmZWF0dXJlIGltcG9ydGFuY2UgYmFyIGNoYXJ0IGJlbG93LCB3ZSBjYW4gc2VlIHRoYXQgdGhlIHRvcCB0aHJlZSBpbXBvcnRhbnQgZmVhdHVyZXMgYXJlIG1hcmtldCBjYXAsIGVhcm5pbmdzLCBNMVNMX1BDMSwgYW5kIEdEUF9QQzEuIAoKYGBge3J9CnNldC5zZWVkKDEwKSAjc2luY2Ugd2UgYXJlIHNhbXBsaW5nICYgcGVybXV0aW5nLCB3ZSBzZXQgYSBzZWVkIHNvIHdlIGNhbiByZXBsaWNhdGUgdGhlIHJlc3VsdHMKcmZfdmFyX2ltcCA8LSAKICBtb2RlbF9wYXJ0cygKICAgIHJmX2V4cGxhaW4KICAgICkKcGxvdChyZl92YXJfaW1wLCBzaG93X2JveHBsb3RzID0gVFJVRSkKYGBgCgpBZnRlciBjcmVhdGluZyB0d28gbW9kZWxzLCB3ZSB0aGVuIG1vdmUgb24gdG8gdGhlIGZpbmFsIG1ldGhvZDogc3RhY2tpbmcuIEZvciB0aGUgc3RhY2tpbmcgbW9kZWwsIHdlIGFsc28gYWRkIG9uZSBtb3JlIG1vZGVsOiBLTk4gdG8gaW1wcm92ZSB0aGUgYWNjdXJhY3kgcmF0ZSBvZiB0aGUgbW9kZWwuCgojIyBTdGFja2luZyBNb2RlbAoKIyMjIFJhbmRvbSBGb3Jlc3QgTW9kZWwgCgpgYGB7cn0KcmFuZ2VyX3JlY2lwZSA8LSAKICByZWNpcGUoZm9ybXVsYSA9IFBST0ZJVCB+IC4sIAogICAgICAgICBkYXRhID0gZGF0YV90cmFpbmluZykgJT4lIAogICMgTWFrZSB0aGVzZSBldmFsdWF0aXZlIHZhcmlhYmxlcywgbm90IGluY2x1ZGVkIGluIG1vZGVsaW5nCiAgdXBkYXRlX3JvbGUoYWxsX29mKGMoIllFQVIiLCAiTmFtZSIsICJTZWN0b3IiKSksCiAgICAgICAgICAgICAgbmV3X3JvbGUgPSAiZXZhbHVhdGl2ZSIpCgpyYW5nZXJfc3BlYyA8LSAKICByYW5kX2ZvcmVzdChtdHJ5ID0gNiwgCiAgICAgICAgICAgICAgbWluX24gPSAxMCwgCiAgICAgICAgICAgICAgdHJlZXMgPSAyMDApICU+JSAKICBzZXRfbW9kZSgicmVncmVzc2lvbiIpICU+JSAKICBzZXRfZW5naW5lKCJyYW5nZXIiKQoKcmFuZ2VyX3dvcmtmbG93IDwtIAogIHdvcmtmbG93KCkgJT4lIAogIGFkZF9yZWNpcGUocmFuZ2VyX3JlY2lwZSkgJT4lIAogIGFkZF9tb2RlbChyYW5nZXJfc3BlYykgCgpyYW5nZXJfZml0IDwtIHJhbmdlcl93b3JrZmxvdyAlPiUgCiAgZml0KGRhdGFfdHJhaW5pbmcpCgpzZXQuc2VlZCgxMjExKSAjIGZvciByZXByb2R1Y2liaWxpdHkKZmluYWxfZGF0YV9jdiA8LSB2Zm9sZF9jdihkYXRhX3RyYWluaW5nLCB2ID0gNSkKCm1ldHJpYyA8LSBtZXRyaWNfc2V0KHJtc2UpCmN0cmxfcmVzIDwtIGNvbnRyb2xfc3RhY2tfcmVzYW1wbGVzKCkKCnJhbmdlcl9jdiA8LSByYW5nZXJfd29ya2Zsb3cgJT4lIAogIGZpdF9yZXNhbXBsZXMoZmluYWxfZGF0YV9jdiwgCiAgICAgICAgICAgICAgICBtZXRyaWNzID0gbWV0cmljLAogICAgICAgICAgICAgICAgY29udHJvbCA9IGN0cmxfcmVzKQpgYGAKCiMjIyBMQVNTTyBNb2RlbCAKCmBgYHtyfQojIGxhc3NvIHJlY2lwZSBhbmQgdHJhbnNmb3JtYXRpb24gc3RlcHMKbGFzc29fZmluYWxfZGF0YV9yZWNpcGUgPC0gcmVjaXBlKFBST0ZJVCB+IC4sIAogICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRhX3RyYWluaW5nKSAlPiUgCiAgI3N0ZXBfcm0oTmFtZSwgU2VjdG9yLCBZRUFSLCBDT01QQU5ZKSAlPiUKICB1cGRhdGVfcm9sZShhbGxfb2YoYygiTmFtZSIsCiAgICAgICAgICAgICAgICAgICAgICAgIlNlY3RvciIsCiAgICAgICAgICAgICAgICAgICAgICAgIllFQVIiKSksCiAgICAgICAgICAgICAgbmV3X3JvbGUgPSAiZXZhbHVhdGl2ZSIpICU+JSAKICBzdGVwX2R1bW15KGFsbF9ub21pbmFsKCksIAogICAgICAgICAgICAgLWFsbF9vdXRjb21lcygpLCAKICAgICAgICAgICAgIC1oYXNfcm9sZShtYXRjaCA9ICJldmFsdWF0aXZlIikpICU+JSAKICBzdGVwX25vcm1hbGl6ZShhbGxfcHJlZGljdG9ycygpLCAKICAgICAgICAgICAgICAgICAtYWxsX25vbWluYWwoKSkKCiNkZWZpbmUgbGFzc28gbW9kZWwKbGFzc29fbW9kIDwtIAogIGxpbmVhcl9yZWcobWl4dHVyZSA9IDEpICU+JSAKICBzZXRfZW5naW5lKCJnbG1uZXQiKSAlPiUgCiAgc2V0X2FyZ3MocGVuYWx0eSA9IHR1bmUoKSkgJT4lIAogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikKCiMgY3JlYXRlIHdvcmtmbG93Cmxhc3NvX3dmIDwtIAogIHdvcmtmbG93KCkgJT4lIAogIGFkZF9yZWNpcGUobGFzc29fZmluYWxfZGF0YV9yZWNpcGUpICU+JSAKICBhZGRfbW9kZWwobGFzc29fbW9kKQoKIyBwZW5hbHR5IGdyaWQgLSBjaGFuZ2VkIHRvIDEwIGxldmVscwpwZW5hbHR5X2dyaWQgPC0gZ3JpZF9yZWd1bGFyKHBlbmFsdHkoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSAxMCkKCiMgYWRkIGN0cmxfZ3JpZCAtIGFzc3VyZXMgcHJlZGljdGlvbnMgYW5kIHdvcmtmbG93cyBhcmUgc2F2ZWQKY3RybF9ncmlkIDwtIGNvbnRyb2xfc3RhY2tfZ3JpZCgpCgojIHR1bmUgdGhlIG1vZGVsIHVzaW5nIHRoZSBzYW1lIGN2IHNhbXBsZXMgYXMgcmFuZG9tIGZvcmVzdAoKbGFzc29fdHVuZSA8LSAKICBsYXNzb193ZiAlPiUgCiAgdHVuZV9ncmlkKAogICAgcmVzYW1wbGVzID0gZmluYWxfZGF0YV9jdiwKICAgIGdyaWQgPSBwZW5hbHR5X2dyaWQsCiAgICBtZXRyaWNzID0gbWV0cmljLAogICAgY29udHJvbCA9IGN0cmxfZ3JpZAogICAgKQpgYGAKCiMjIyBLTk4gTW9kZWwKCmBgYHtyfQojIGNyZWF0ZSBhIG1vZGVsIGRlZmluaXRpb24Ka25uX21vZCA8LQogIG5lYXJlc3RfbmVpZ2hib3IoCiAgICBuZWlnaGJvcnMgPSB0dW5lKCJrIikKICApICU+JQogIHNldF9lbmdpbmUoImtrbm4iKSAlPiUgCiAgc2V0X21vZGUoInJlZ3Jlc3Npb24iKQoKIyBjcmVhdGUgdGhlIHdvcmtmbG93Cmtubl93ZiA8LSAKICB3b3JrZmxvdygpICU+JSAKICBhZGRfbW9kZWwoa25uX21vZCkgJT4lCiAgYWRkX3JlY2lwZShsYXNzb19maW5hbF9kYXRhX3JlY2lwZSkKCiMgdHVuZSBpdCB1c2luZyA0IHR1bmluZyBwYXJhbWV0ZXJzCmtubl90dW5lIDwtIAogIGtubl93ZiAlPiUgCiAgdHVuZV9ncmlkKAogICAgZmluYWxfZGF0YV9jdiwKICAgIG1ldHJpY3MgPSBtZXRyaWMsCiAgICBncmlkID0gNCwKICAgIGNvbnRyb2wgPSBjdHJsX2dyaWQKICApCmBgYAoKCiMjIyBTdGFja2luZyBhbGwgdGhyZWUgbW9kZWxzIAoKYGBge3J9CmZpbmFsX2RhdGFfc3RhY2sgPC0gCiAgc3RhY2tzKCkgJT4lIAogIGFkZF9jYW5kaWRhdGVzKHJhbmdlcl9jdikgJT4lIAogIGFkZF9jYW5kaWRhdGVzKGxhc3NvX3R1bmUpICU+JSAKICBhZGRfY2FuZGlkYXRlcyhrbm5fdHVuZSkKYGBgCgpgYGB7cn0KZmluYWxfZGF0YV9ibGVuZCA8LSAKICBmaW5hbF9kYXRhX3N0YWNrICU+JSAKICBibGVuZF9wcmVkaWN0aW9ucygpCmBgYAoKYGBge3J9CmZpbmFsX2RhdGFfYmxlbmQKYGBgCgpgYGB7cn0KZmluYWxfZGF0YV9ibGVuZCRtZXRyaWNzICU+JSAKICBmaWx0ZXIoLm1ldHJpYyA9PSAicm1zZSIpICU+JSAKICBzdW1tYXJpc2UobWVhbl9ybXNlID0gbWVhbihtZWFuKSkKYGBgCgpgYGB7cn0KYXV0b3Bsb3QoZmluYWxfZGF0YV9ibGVuZCkKYGBgCgpgYGB7cn0KZmluYWxfZGF0YV9maW5hbF9zdGFjayA8LSBmaW5hbF9kYXRhX2JsZW5kICU+JSAKICBmaXRfbWVtYmVycygpCmBgYAoKYGBge3J9CmZpbmFsX2RhdGFfZmluYWxfc3RhY2sgJT4lIAogIHByZWRpY3QobmV3X2RhdGEgPSBkYXRhX3Rlc3RpbmcpICU+JSAKICBiaW5kX2NvbHMoZGF0YV90ZXN0aW5nKSAlPiUgCiAgc2VsZWN0KE5hbWUsIC5wcmVkLCBQUk9GSVQpICU+JSAKICBhcnJhbmdlKGRlc2MoLnByZWQpKSAlPiUgCiAgaGVhZCgxMCkKYGBgCgpIZXJlLCB3ZSBjYW4gc2VlIHRoYXQgd2hlbiB0aGUgbW9kZWwgcHJlZGljdHMgY29tcGFueSB3aXRoIGhpZ2ggcmF0ZSBvZiByZXR1cm4sIHRoZSBtb2RlbCBwZXJmb3JtcyByZWFsbHkgd2VsbC4gCgojIyMgQ29tcGFyaXNvbiBvZiB0aGUgdGhyZWUgbW9kZWxzCgpXaXRoIHRoZSB0aHJlZSBtb2RlbHMsIHdlIHRoZW4gbW92ZSBvbiB0byBzZWUgd2hpY2ggbW9kZWwgcGVyZm9ybXMgdGhlIGJlc3Q6IAoKIyMjIyBMYXNzbyBtb2RlbApgYGB7cn0KZGF0YV9mcmFtZShyZXR1cm5fbG1fdHVuZSAlPiUKICBzZWxlY3QoaWQsIC5tZXRyaWNzKSAlPiUKICB1bm5lc3QoLm1ldHJpY3MpICU+JQogIGZpbHRlcigubWV0cmljID09ICJyc3EiKSAlPiUgCiAgZmlsdGVyKC5jb25maWcgPT0gIlByZXByb2Nlc3NvcjFfTW9kZWwwOCIpICU+JSAKICBzdW1tYXJpc2UobWVhbl9yc3EgPSBtZWFuKC5lc3RpbWF0ZSkpLAoKcmV0dXJuX2xtX3R1bmUgJT4lCiAgc2VsZWN0KGlkLCAubWV0cmljcykgJT4lCiAgdW5uZXN0KC5tZXRyaWNzKSAlPiUKICBmaWx0ZXIoLm1ldHJpYyA9PSAicm1zZSIpICU+JSAKICBmaWx0ZXIoLmNvbmZpZyA9PSAiUHJlcHJvY2Vzc29yMV9Nb2RlbDA4IikgJT4lIAogIHN1bW1hcmlzZShtZWFuX3Jtc2UgPSBtZWFuKC5lc3RpbWF0ZSkpKQpgYGAKCiMjIyMgUmFuZG9tIEZvcmVzdCBNb2RlbAoKYGBge3J9CiNPT0IgUk1TRQpkYXRhLmZyYW1lKG1lYW5fcm1zZSA9IHNxcnQocmFuZ2VyX2ZpdCRmaXQkZml0JGZpdCRwcmVkaWN0aW9uLmVycm9yKSwKIyBSIHNxdWFyZWQKbWVhbl9yc3EgPSByYW5nZXJfZml0JGZpdCRmaXQkZml0JHIuc3F1YXJlZCkKYGBgCgoKIyMjIyBTdGFja2luZyBNb2RlbCAKCmBgYHtyfQojIFN0YWNraW5nIG1vZGVsOiAKZGF0YS5mcmFtZShmaW5hbF9kYXRhX2JsZW5kJG1ldHJpY3MgJT4lIAogIGZpbHRlcigubWV0cmljID09ICJybXNlIikgJT4lIAogIHN1bW1hcmlzZShtZWFuX3Jtc2UgPSBtZWFuKG1lYW4pKSwKCmZpbmFsX2RhdGFfYmxlbmQkbWV0cmljcyAlPiUgCiAgZmlsdGVyKC5tZXRyaWMgPT0gInJzcSIpICU+JSAKICBzdW1tYXJpc2UobWVhbl9yc3EgPSBtZWFuKG1lYW4pKSkKYGBgCkhlcmUsIHdlIGNhbiBzZWUgdGhhdCBjb21wYXJlZCB0byB0aGUgdGhyZWUgbW9kZWxzLCBldmVuIHRob3VnaCBSYW5kb20gRm9yZXN0IHBlcmZvcm1zIGJldHRlciB0aGFuIHRoZSBzdGFja2luZyBtb2RlbCwgc3RhY2tpbmcgbW9kZWwgdXNlIGZlYXR1cmVzIGluIHRoZSBSYW5kb20gRm9yZXN0IGFsb25nIHdpdGggYWRkaXRpb25hbCBmZWF0dXJlcyBmcm9tIEtOTiBhbmQgbGFzc28uIFdpdGggdGhhdCByZWFzb24sIHdlIHdpbGwgY2hvb3NlIHN0YWNraW5nIG1vZGVsIGFzIG91ciBtb2RlbCBjaG9pY2UuIAoKIyMgU3RvY2sgUmV0dXJuIFByZWRpY3Rpb24gZm9yIDIwMjEKCkFmdGVyIHBpY2tpbmcgb3VyIG1vZGVsLCB3ZSB0aGVuIG1vdmUgb24gdG8gdXNlIHRoZSBtb2RlbCB0byBwcmVkaWN0IHRoZSBwb3RlbnRpYWwgcHJvZml0IGZvciAyMDIxOiAKCmBgYHtyfQpzZXQuc2VlZCg0NTYpCnl0ZCA9IGMoODAuMzAsIDM5Ljc3LCAtOC4yNiwgMTEuMjksIDIyLjA4LCAxMzIuNTEsIDEzNy4wNCwgLTQuMjIsIDM5Ljc3LCAyMC43NiwgMjYuNDEsIDI2LjM0LCA0MS41MCwgMTc4LjQzLCAxOC43MCwgODcuMjksIC02LjE2LCA1Mi41OCwgOC43MCwgLTEzLjcwKQpzZXQuc2VlZCg0NTYpCnByZWRfMjAyMTwtZmluYWxfZGF0YV9maW5hbF9zdGFjayAlPiUgCiAgcHJlZGljdChuZXdfZGF0YSA9IGZpbmFsX2RhdGFfMjAyMSkgJT4lIAogIGJpbmRfY29scyhmaW5hbF9kYXRhXzIwMjEpICU+JSAKICBzZWxlY3QoTmFtZSwgLnByZWQpICU+JSAKICBhcnJhbmdlKGRlc2MoLnByZWQpKSAlPiUgCiAgaGVhZCgyMCkgJT4lIAogIG11dGF0ZShhY3R1YWxfeXRkID0geXRkKSAKcHJlZF8yMDIxICU+JSAKICBrYmwoKSAlPiUgCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiYm9yZGVyZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIikpICU+JSAKICBjb2x1bW5fc3BlYyhjKDEsMyksIGNvbG9yID0gaWZlbHNlKHByZWRfMjAyMSRhY3R1YWxfeXRkIDwgMCwgInJlZCIsICJkYXJrZ3JlZW4iKSkgJT4lIAogIHNjcm9sbF9ib3god2lkdGggPSAiMTAwJSIsIGhlaWdodCA9ICI1MDBweCIpIApgYGAKCiMjIENvbmNsdXNpb24gCgpUaGUgd2Vha25lc3Mgb2Ygb3VyIG1vZGVsIGlzIHRoYXQgdGhlIGF2ZXJhZ2UgZXJyb3Igb2YgcHJlZGljdGlvbiBpcyBzdGlsbCB2ZXJ5IGhpZ2ggc28gaWYgb25lIHdhbnRzIHRvIHByZWRpY3QgdGhlIGV4YWN0IHJldHVybiwgb3VyIG1vZGVsIHdvbid0IGJlIGlkZWFsLiBCdXQgdGhlIHN0cmVuZ3RoIG9mIG91ciBtb2RlbCBpcyB0aGF0IGZvciBzdG9ja3Mgd2l0aCBleHRyZW1lbHkgaGlnaCByZXR1cm5zLCBldmVuIHRob3VnaCB0aGUgcHJlZGljdGlvbiBtaWdodCBub3QgYmUgdGhhdCBwcmVjaXNlLCBoaWdobHkgbGlrZWx5LCBvdXIgbW9kZWwgd2lsbCBwcmVkaWN0IHBvc2l0aXZlIHJldHVybnMuIFRoYXQgbWVhbnMsIGluIHJlYWwgbGlmZSwgaWYgd2UgY2hvb3NlIHRoZSB0b3Agc3RvY2tzIHRvIGludmVzdCBpbiBiYXNlZCBvbiBvdXIgcHJlZGljdGlvbiwgaXQgaXMgbGVzcyBsaWtlbHkgd2UgYXJlIGdvaW5nIHRvIGxvc2UgbW9uZXkuIEFub3RoZXIgc3RyZW5ndGggb2YgdXNpbmcgYSBtb2RlbCB0byBoZWxwIHdpdGggaW52ZXN0bWVudCBpcyB0aGF0IGl0IGV4Y2x1ZGVzIG91ciBzdWJqZWN0aXZlIGZlZWxpbmdzLiAKClRvIG1ha2UgdGhlIG1vZGVsIGJldHRlciwgd2UgY291bGQgZG8gbW9yZSByZXNlYXJjaCBhbmQgYWRkIG1vcmUgcmVncmVzc29ycy4gRm9yIGV4YW1wbGUsIHNvbWUgZmluYW5jaWFsIGluZGljYXRvcnMgdGhhdCBhcmUgaW1wb3J0YW50IGZvciB2YWx1ZSBpbnZlc3RpbmcgYXJlIG5vdCByZWZsZWN0ZWQgaW4gb3VyIG1vZGVsIGR1ZSB0byB0aGUgbGFjayBvZiBkYXRhLiAgRXhhbXBsZXMgb2YgdGhvc2UgdmFyaWFibGVzIGluY2x1ZGUgUHJpY2UgdG8gU2FsZXMsIFByaWNlIHRvIENhc2ggRmxvdywgYW5kIFByaWNlIHRvIEJvb2suIEFsc28sIHNpbmNlIHdlIGFyZSBjb25jZXJuZWQgYWJvdXQgdGhlIGxvbmctdGVybSByZXR1cm4gYW5kIGFsbCBmdW5kYW1lbnRhbCBmYWN0b3JzIHVzdWFsbHkgdGFrZSBsb25nZXIgdG8gYWZmZWN0IHRoZSBmaXJtcywgaXQncyBwcm9iYWJseSBoZWxwZnVsIHRvIGRvIHJldHVybiBpbiAyIHllYXJzIG9yIDMgeWVhcnMgb3IgaW5jbHVkZSBsYWcgb2Ygc29tZSB2YXJpYWJsZXMgaW4gb3VyIG1vZGVsLiAgCgoK